summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java7
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java13
-rw-r--r--core/java/android/accounts/AccountManagerService.java91
-rw-r--r--core/java/android/accounts/ChooseTypeAndAccountActivity.java286
-rw-r--r--core/java/android/animation/ArgbEvaluator.java4
-rw-r--r--core/java/android/animation/Keyframe.java10
-rw-r--r--core/java/android/animation/TimeAnimator.java6
-rwxr-xr-xcore/java/android/animation/ValueAnimator.java44
-rw-r--r--core/java/android/app/ActionBar.java9
-rw-r--r--core/java/android/app/Activity.java155
-rw-r--r--core/java/android/app/ActivityManager.java135
-rw-r--r--core/java/android/app/ActivityManagerNative.java475
-rw-r--r--core/java/android/app/ActivityOptions.java41
-rw-r--r--core/java/android/app/ActivityThread.java551
-rw-r--r--core/java/android/app/AlertDialog.java23
-rw-r--r--core/java/android/app/ApplicationErrorReport.java4
-rw-r--r--core/java/android/app/ApplicationPackageManager.java234
-rw-r--r--core/java/android/app/ApplicationThreadNative.java14
-rw-r--r--core/java/android/app/BackStackRecord.java158
-rw-r--r--core/java/android/app/ContextImpl.java423
-rw-r--r--core/java/android/app/Dialog.java30
-rw-r--r--core/java/android/app/DownloadManager.java8
-rw-r--r--core/java/android/app/Fragment.java373
-rw-r--r--core/java/android/app/FragmentManager.java279
-rw-r--r--core/java/android/app/IActivityManager.java82
-rw-r--r--core/java/android/app/IApplicationThread.java7
-rw-r--r--core/java/android/app/IInstrumentationWatcher.aidl2
-rw-r--r--core/java/android/app/INotificationManager.aidl11
-rw-r--r--core/java/android/app/ISearchManager.aidl1
-rw-r--r--core/java/android/app/IStopUserCallback.aidl27
-rw-r--r--core/java/android/app/IUserSwitchObserver.aidl25
-rw-r--r--core/java/android/app/IWallpaperManager.aidl5
-rw-r--r--core/java/android/app/Instrumentation.java79
-rw-r--r--core/java/android/app/KeyguardManager.java5
-rw-r--r--core/java/android/app/LoadedApk.java34
-rw-r--r--core/java/android/app/LoaderManager.java6
-rw-r--r--core/java/android/app/MediaRouteActionProvider.java4
-rw-r--r--core/java/android/app/MediaRouteButton.java100
-rw-r--r--core/java/android/app/NativeActivity.java8
-rw-r--r--core/java/android/app/Notification.java43
-rw-r--r--core/java/android/app/NotificationManager.java55
-rw-r--r--core/java/android/app/PendingIntent.java163
-rw-r--r--core/java/android/app/Presentation.java258
-rw-r--r--core/java/android/app/SearchManager.java29
-rw-r--r--core/java/android/app/StatusBarManager.java27
-rw-r--r--core/java/android/app/TaskStackBuilder.java112
-rw-r--r--core/java/android/app/WallpaperManager.java44
-rw-r--r--core/java/android/app/admin/DeviceAdminInfo.java94
-rwxr-xr-xcore/java/android/app/admin/DevicePolicyManager.java232
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl117
-rw-r--r--core/java/android/app/backup/WallpaperBackupHelper.java14
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java109
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java75
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java103
-rw-r--r--core/java/android/appwidget/AppWidgetProviderInfo.java80
-rw-r--r--core/java/android/bluetooth/AtCommandHandler.java94
-rw-r--r--core/java/android/bluetooth/AtCommandResult.java115
-rw-r--r--core/java/android/bluetooth/AtParser.java367
-rwxr-xr-x[-rw-r--r--]core/java/android/bluetooth/BluetoothA2dp.java182
-rwxr-xr-x[-rw-r--r--]core/java/android/bluetooth/BluetoothAdapter.java368
-rw-r--r--core/java/android/bluetooth/BluetoothAudioGateway.java202
-rwxr-xr-x[-rw-r--r--]core/java/android/bluetooth/BluetoothDevice.java155
-rw-r--r--core/java/android/bluetooth/BluetoothDeviceProfileState.java1357
-rwxr-xr-x[-rw-r--r--]core/java/android/bluetooth/BluetoothHeadset.java243
-rw-r--r--core/java/android/bluetooth/BluetoothHealth.java99
-rwxr-xr-x[-rw-r--r--]core/java/android/bluetooth/BluetoothInputDevice.java369
-rw-r--r--core/java/android/bluetooth/BluetoothPan.java132
-rwxr-xr-x[-rw-r--r--]core/java/android/bluetooth/BluetoothPbap.java69
-rwxr-xr-x[-rw-r--r--]core/java/android/bluetooth/BluetoothProfile.java0
-rw-r--r--core/java/android/bluetooth/BluetoothProfileState.java160
-rw-r--r--core/java/android/bluetooth/BluetoothServerSocket.java22
-rw-r--r--core/java/android/bluetooth/BluetoothSocket.java481
-rw-r--r--core/java/android/bluetooth/BluetoothTetheringDataTracker.java115
-rw-r--r--core/java/android/bluetooth/HeadsetBase.java298
-rw-r--r--core/java/android/bluetooth/IBluetooth.aidl95
-rw-r--r--core/java/android/bluetooth/IBluetoothA2dp.aidl8
-rw-r--r--core/java/android/bluetooth/IBluetoothCallback.aidl3
-rw-r--r--core/java/android/bluetooth/IBluetoothHeadset.aidl12
-rw-r--r--core/java/android/bluetooth/IBluetoothHeadsetPhone.aidl27
-rw-r--r--core/java/android/bluetooth/IBluetoothHealth.aidl30
-rwxr-xr-xcore/java/android/bluetooth/IBluetoothInputDevice.aidl46
-rwxr-xr-xcore/java/android/bluetooth/IBluetoothManager.aidl29
-rw-r--r--core/java/android/bluetooth/IBluetoothManagerCallback.aidl17
-rw-r--r--core/java/android/bluetooth/IBluetoothPan.aidl22
-rw-r--r--core/java/android/content/BroadcastReceiver.java18
-rw-r--r--core/java/android/content/ClipData.java2
-rw-r--r--core/java/android/content/ContentProvider.java6
-rw-r--r--core/java/android/content/ContentProviderClient.java13
-rw-r--r--core/java/android/content/ContentResolver.java26
-rw-r--r--core/java/android/content/ContentService.java182
-rw-r--r--core/java/android/content/Context.java379
-rw-r--r--core/java/android/content/ContextWrapper.java118
-rw-r--r--core/java/android/content/IContentService.aidl22
-rwxr-xr-xcore/java/android/content/IIntentReceiver.aidl4
-rw-r--r--core/java/android/content/Intent.java171
-rw-r--r--core/java/android/content/IntentSender.java62
-rw-r--r--core/java/android/content/SyncManager.java140
-rw-r--r--core/java/android/content/SyncOperation.java19
-rw-r--r--core/java/android/content/SyncStorageEngine.java3
-rw-r--r--core/java/android/content/pm/ActivityInfo.java35
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java17
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl27
-rw-r--r--core/java/android/content/pm/InstrumentationInfo.java4
-rw-r--r--core/java/android/content/pm/ManifestDigest.java16
-rw-r--r--core/java/android/content/pm/PackageCleanItem.aidl18
-rw-r--r--core/java/android/content/pm/PackageCleanItem.java85
-rw-r--r--core/java/android/content/pm/PackageInfoLite.java7
-rw-r--r--core/java/android/content/pm/PackageManager.java381
-rw-r--r--core/java/android/content/pm/PackageParser.java223
-rw-r--r--core/java/android/content/pm/PackageStats.java65
-rw-r--r--core/java/android/content/pm/PackageUserState.java59
-rw-r--r--core/java/android/content/pm/PermissionGroupInfo.java3
-rw-r--r--core/java/android/content/pm/PermissionInfo.java45
-rw-r--r--core/java/android/content/pm/ProviderInfo.java22
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java14
-rw-r--r--core/java/android/content/pm/ResolveInfo.java15
-rw-r--r--core/java/android/content/pm/ServiceInfo.java10
-rw-r--r--core/java/android/content/pm/UserInfo.java53
-rw-r--r--core/java/android/content/pm/VerificationParams.aidl19
-rw-r--r--core/java/android/content/pm/VerificationParams.java236
-rw-r--r--core/java/android/content/res/CompatibilityInfo.java9
-rw-r--r--core/java/android/content/res/Configuration.java275
-rwxr-xr-xcore/java/android/content/res/Resources.java75
-rw-r--r--core/java/android/content/res/TypedArray.java9
-rw-r--r--core/java/android/database/DatabaseUtils.java44
-rw-r--r--core/java/android/ddm/DdmHandleAppName.java12
-rw-r--r--core/java/android/ddm/DdmHandleHello.java4
-rw-r--r--core/java/android/hardware/Camera.java61
-rw-r--r--core/java/android/hardware/Sensor.java9
-rw-r--r--core/java/android/hardware/display/DisplayManager.java258
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java360
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl49
-rw-r--r--core/java/android/hardware/display/IDisplayManagerCallback.aidl22
-rw-r--r--core/java/android/hardware/display/WifiDisplay.aidl19
-rw-r--r--core/java/android/hardware/display/WifiDisplay.java137
-rw-r--r--core/java/android/hardware/display/WifiDisplayStatus.aidl19
-rw-r--r--core/java/android/hardware/display/WifiDisplayStatus.java215
-rw-r--r--core/java/android/hardware/usb/IUsbManager.aidl8
-rw-r--r--core/java/android/hardware/usb/UsbRequest.java12
-rw-r--r--core/java/android/inputmethodservice/AbstractInputMethodService.java29
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java33
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java10
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java57
-rw-r--r--core/java/android/net/BaseNetworkStateTracker.java158
-rw-r--r--core/java/android/net/CaptivePortalTracker.java378
-rw-r--r--core/java/android/net/ConnectivityManager.java60
-rw-r--r--core/java/android/net/DhcpStateMachine.java8
-rw-r--r--core/java/android/net/DnsPinger.java4
-rw-r--r--core/java/android/net/DummyDataStateTracker.java4
-rw-r--r--core/java/android/net/EthernetDataTracker.java13
-rw-r--r--core/java/android/net/IConnectivityManager.aidl7
-rw-r--r--core/java/android/net/INetworkManagementEventObserver.aidl7
-rw-r--r--core/java/android/net/INetworkPolicyManager.aidl6
-rw-r--r--core/java/android/net/LocalSocket.java13
-rw-r--r--core/java/android/net/MobileDataStateTracker.java5
-rw-r--r--core/java/android/net/NetworkInfo.java5
-rw-r--r--core/java/android/net/NetworkPolicyManager.java18
-rw-r--r--core/java/android/net/NetworkStateTracker.java11
-rw-r--r--core/java/android/net/NetworkStats.java5
-rw-r--r--core/java/android/net/ParseException.java5
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java52
-rw-r--r--core/java/android/net/Uri.java38
-rw-r--r--core/java/android/net/arp/ArpPeer.java42
-rw-r--r--core/java/android/net/http/CertificateChainValidator.java8
-rw-r--r--core/java/android/net/http/X509TrustManagerExtensions.java66
-rw-r--r--core/java/android/nfc/NdefRecord.java3
-rw-r--r--core/java/android/nfc/Tag.java4
-rw-r--r--core/java/android/nfc/tech/Ndef.java4
-rw-r--r--core/java/android/nfc/tech/NfcBarcode.java102
-rw-r--r--core/java/android/nfc/tech/TagTechnology.java9
-rw-r--r--core/java/android/os/BatteryManager.java2
-rw-r--r--core/java/android/os/BatteryStats.java51
-rw-r--r--core/java/android/os/Binder.java34
-rw-r--r--core/java/android/os/Build.java22
-rw-r--r--core/java/android/os/Debug.java2
-rw-r--r--core/java/android/os/Environment.java250
-rw-r--r--core/java/android/os/FactoryTest.java35
-rw-r--r--core/java/android/os/Handler.java262
-rw-r--r--core/java/android/os/INetworkManagementService.aidl44
-rw-r--r--core/java/android/os/IPowerManager.aidl38
-rw-r--r--core/java/android/os/IUserManager.aidl40
-rw-r--r--core/java/android/os/LocalPowerManager.java48
-rw-r--r--core/java/android/os/PowerManager.java915
-rw-r--r--core/java/android/os/Process.java52
-rw-r--r--core/java/android/os/RecoverySystem.java11
-rw-r--r--core/java/android/os/RemoteCallbackList.java21
-rw-r--r--core/java/android/os/SchedulingPolicyService.java2
-rw-r--r--core/java/android/os/SystemClock.java26
-rw-r--r--core/java/android/os/SystemService.java125
-rw-r--r--core/java/android/os/Trace.java3
-rw-r--r--core/java/android/os/UEventObserver.java196
-rw-r--r--core/java/android/os/UserHandle.aidl19
-rw-r--r--core/java/android/os/UserHandle.java260
-rw-r--r--core/java/android/os/UserId.java116
-rw-r--r--core/java/android/os/UserManager.java299
-rw-r--r--core/java/android/os/WorkSource.java32
-rw-r--r--core/java/android/os/storage/IMountService.java53
-rw-r--r--core/java/android/os/storage/StorageManager.java80
-rw-r--r--core/java/android/os/storage/StorageVolume.java112
-rw-r--r--core/java/android/preference/TwoStatePreference.java12
-rw-r--r--core/java/android/provider/CalendarContract.java24
-rw-r--r--core/java/android/provider/CallLog.java20
-rwxr-xr-xcore/java/android/provider/ContactsContract.java103
-rw-r--r--core/java/android/provider/MediaStore.java71
-rw-r--r--core/java/android/provider/Settings.java3337
-rw-r--r--core/java/android/server/BluetoothA2dpService.java653
-rw-r--r--core/java/android/server/BluetoothAdapterProperties.java105
-rw-r--r--core/java/android/server/BluetoothAdapterStateMachine.java822
-rw-r--r--core/java/android/server/BluetoothBondState.java497
-rw-r--r--core/java/android/server/BluetoothDeviceProperties.java140
-rw-r--r--core/java/android/server/BluetoothEventLoop.java1067
-rw-r--r--core/java/android/server/BluetoothHealthProfileHandler.java671
-rw-r--r--core/java/android/server/BluetoothInputProfileHandler.java227
-rw-r--r--core/java/android/server/BluetoothPanProfileHandler.java409
-rwxr-xr-xcore/java/android/server/BluetoothService.java2924
-rw-r--r--core/java/android/server/search/SearchManagerService.java108
-rw-r--r--core/java/android/server/search/Searchables.java121
-rw-r--r--core/java/android/service/dreams/Dream.java399
-rw-r--r--core/java/android/service/dreams/DreamManagerService.java184
-rw-r--r--core/java/android/service/dreams/DreamService.java625
-rw-r--r--core/java/android/service/dreams/IDreamManager.aidl8
-rw-r--r--core/java/android/service/wallpaper/IWallpaperConnection.aidl1
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java40
-rw-r--r--core/java/android/speech/tts/BlockingAudioTrack.java47
-rw-r--r--core/java/android/speech/tts/FileSynthesisCallback.java27
-rw-r--r--core/java/android/speech/tts/SynthesisPlaybackQueueItem.java6
-rwxr-xr-xcore/java/android/speech/tts/TextToSpeech.java29
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java37
-rw-r--r--core/java/android/test/AndroidTestCase.java3
-rw-r--r--core/java/android/text/Spanned.java15
-rw-r--r--core/java/android/text/StaticLayout.java62
-rw-r--r--core/java/android/text/TextDirectionHeuristics.java4
-rw-r--r--core/java/android/text/TextUtils.java74
-rw-r--r--core/java/android/text/format/DateFormat.java63
-rw-r--r--core/java/android/text/format/DateUtils.java241
-rw-r--r--core/java/android/text/format/Time.java107
-rw-r--r--core/java/android/text/style/LocaleSpan.java84
-rw-r--r--core/java/android/util/AtomicFile.java233
-rw-r--r--core/java/android/util/DisplayMetrics.java67
-rw-r--r--core/java/android/util/FloatMath.java29
-rw-r--r--core/java/android/util/LocaleUtil.java85
-rw-r--r--core/java/android/util/LruCache.java7
-rw-r--r--core/java/android/util/NtpTrustedTime.java18
-rw-r--r--core/java/android/util/Pair.java33
-rw-r--r--core/java/android/util/Spline.java156
-rw-r--r--core/java/android/util/TimeUtils.java27
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java245
-rw-r--r--core/java/android/view/Choreographer.java30
-rw-r--r--core/java/android/view/ContextThemeWrapper.java38
-rw-r--r--core/java/android/view/Display.java566
-rw-r--r--core/java/android/view/DisplayEventReceiver.java36
-rw-r--r--core/java/android/view/DisplayInfo.aidl19
-rw-r--r--core/java/android/view/DisplayInfo.java310
-rw-r--r--core/java/android/view/DisplayList.java7
-rw-r--r--core/java/android/view/FocusFinder.java47
-rw-r--r--core/java/android/view/GLES20Canvas.java31
-rw-r--r--core/java/android/view/GLES20DisplayList.java16
-rw-r--r--core/java/android/view/GLES20Layer.java23
-rw-r--r--core/java/android/view/GLES20RecordingCanvas.java8
-rw-r--r--core/java/android/view/GLES20RenderLayer.java26
-rw-r--r--core/java/android/view/GLES20TextureLayer.java18
-rw-r--r--core/java/android/view/GestureDetector.java21
-rw-r--r--core/java/android/view/Gravity.java16
-rw-r--r--core/java/android/view/HardwareCanvas.java16
-rw-r--r--core/java/android/view/HardwareLayer.java31
-rw-r--r--core/java/android/view/HardwareRenderer.java182
-rw-r--r--core/java/android/view/IDisplayContentChangeListener.aidl32
-rw-r--r--core/java/android/view/IInputFilter.aidl32
-rw-r--r--core/java/android/view/IInputFilterHost.aidl28
-rw-r--r--core/java/android/view/IWindow.aidl3
-rw-r--r--core/java/android/view/IWindowManager.aidl62
-rw-r--r--core/java/android/view/IWindowSession.aidl21
-rw-r--r--core/java/android/view/InputEventConsistencyVerifier.java2
-rw-r--r--core/java/android/view/InputEventReceiver.java10
-rw-r--r--core/java/android/view/InputFilter.java261
-rw-r--r--core/java/android/view/LayoutInflater.java100
-rw-r--r--core/java/android/view/ScaleGestureDetector.java164
-rw-r--r--core/java/android/view/Surface.java1001
-rw-r--r--core/java/android/view/SurfaceSession.java42
-rw-r--r--core/java/android/view/SurfaceView.java45
-rw-r--r--core/java/android/view/TextureView.java5
-rw-r--r--core/java/android/view/View.java2601
-rw-r--r--core/java/android/view/ViewConfiguration.java28
-rw-r--r--core/java/android/view/ViewDebug.java71
-rw-r--r--core/java/android/view/ViewGroup.java517
-rw-r--r--core/java/android/view/ViewParent.java12
-rw-r--r--core/java/android/view/ViewPropertyAnimator.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java509
-rw-r--r--core/java/android/view/ViewTreeObserver.java179
-rw-r--r--core/java/android/view/VolumePanel.java97
-rw-r--r--core/java/android/view/Window.java129
-rw-r--r--core/java/android/view/WindowInfo.aidl20
-rw-r--r--core/java/android/view/WindowInfo.java175
-rw-r--r--core/java/android/view/WindowManager.java117
-rw-r--r--core/java/android/view/WindowManagerGlobal.java516
-rw-r--r--core/java/android/view/WindowManagerImpl.java642
-rw-r--r--core/java/android/view/WindowManagerPolicy.java102
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java98
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java60
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java139
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeProvider.java56
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl30
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl16
-rw-r--r--core/java/android/view/animation/RotateAnimation.java2
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.java9
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java296
-rw-r--r--core/java/android/view/inputmethod/InputMethodSession.java15
-rw-r--r--core/java/android/view/inputmethod/InputMethodSubtype.java56
-rw-r--r--core/java/android/webkit/AccessibilityInjector.java62
-rw-r--r--core/java/android/webkit/BrowserDownloadListener.java57
-rw-r--r--core/java/android/webkit/BrowserFrame.java87
-rw-r--r--core/java/android/webkit/CacheManager.java1
-rw-r--r--core/java/android/webkit/CallbackProxy.java109
-rw-r--r--core/java/android/webkit/CertTool.java3
-rw-r--r--core/java/android/webkit/CookieManager.java4
-rw-r--r--core/java/android/webkit/CookieSyncManager.java4
-rw-r--r--core/java/android/webkit/DateSorter.java12
-rwxr-xr-xcore/java/android/webkit/DeviceOrientationService.java2
-rw-r--r--core/java/android/webkit/HTML5VideoFullScreen.java65
-rw-r--r--core/java/android/webkit/HTML5VideoInline.java26
-rw-r--r--core/java/android/webkit/HTML5VideoView.java38
-rw-r--r--core/java/android/webkit/HTML5VideoViewProxy.java94
-rw-r--r--core/java/android/webkit/HttpAuthHandler.java31
-rw-r--r--core/java/android/webkit/JavascriptInterface.java35
-rw-r--r--core/java/android/webkit/JniUtil.java4
-rw-r--r--core/java/android/webkit/MockGeolocation.java39
-rw-r--r--core/java/android/webkit/SearchBox.java109
-rw-r--r--core/java/android/webkit/SearchBoxImpl.java304
-rw-r--r--core/java/android/webkit/SslErrorHandler.java4
-rw-r--r--core/java/android/webkit/ViewStateSerializer.java10
-rw-r--r--core/java/android/webkit/WebBackForwardList.java132
-rw-r--r--core/java/android/webkit/WebBackForwardListClassic.java166
-rw-r--r--core/java/android/webkit/WebChromeClient.java18
-rw-r--r--core/java/android/webkit/WebHistoryItem.java184
-rw-r--r--core/java/android/webkit/WebHistoryItemClassic.java221
-rw-r--r--core/java/android/webkit/WebSettings.java323
-rw-r--r--core/java/android/webkit/WebSettingsClassic.java70
-rw-r--r--core/java/android/webkit/WebStorage.java18
-rw-r--r--core/java/android/webkit/WebSyncManager.java4
-rw-r--r--core/java/android/webkit/WebView.java213
-rw-r--r--core/java/android/webkit/WebViewClassic.java733
-rw-r--r--core/java/android/webkit/WebViewClient.java45
-rw-r--r--core/java/android/webkit/WebViewClientClassicExt.java53
-rw-r--r--core/java/android/webkit/WebViewCore.java113
-rw-r--r--core/java/android/webkit/WebViewDatabase.java45
-rw-r--r--core/java/android/webkit/WebViewDatabaseClassic.java14
-rw-r--r--core/java/android/webkit/WebViewFactory.java63
-rw-r--r--core/java/android/webkit/WebViewFactoryProvider.java6
-rw-r--r--core/java/android/webkit/WebViewProvider.java7
-rw-r--r--core/java/android/webkit/ZoomControlEmbedded.java2
-rw-r--r--core/java/android/webkit/ZoomManager.java13
-rw-r--r--core/java/android/widget/AbsListView.java203
-rw-r--r--core/java/android/widget/AbsSeekBar.java44
-rw-r--r--core/java/android/widget/ActivityChooserModel.java8
-rw-r--r--core/java/android/widget/ActivityChooserView.java2
-rw-r--r--core/java/android/widget/AdapterViewAnimator.java16
-rwxr-xr-xcore/java/android/widget/AppSecurityPermissions.java735
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java33
-rw-r--r--core/java/android/widget/CalendarView.java128
-rw-r--r--core/java/android/widget/CheckedTextView.java65
-rw-r--r--core/java/android/widget/CompoundButton.java39
-rw-r--r--core/java/android/widget/DatePicker.java2
-rw-r--r--core/java/android/widget/DigitalClock.java14
-rw-r--r--core/java/android/widget/Editor.java34
-rw-r--r--core/java/android/widget/FastScroller.java10
-rw-r--r--core/java/android/widget/FrameLayout.java12
-rw-r--r--core/java/android/widget/Gallery.java18
-rw-r--r--core/java/android/widget/GridView.java22
-rw-r--r--core/java/android/widget/ImageView.java32
-rw-r--r--core/java/android/widget/LinearLayout.java30
-rw-r--r--core/java/android/widget/ListPopupWindow.java25
-rw-r--r--core/java/android/widget/ListView.java62
-rw-r--r--core/java/android/widget/NumberPicker.java200
-rw-r--r--core/java/android/widget/OverScroller.java29
-rw-r--r--core/java/android/widget/PopupWindow.java8
-rw-r--r--core/java/android/widget/ProgressBar.java57
-rw-r--r--core/java/android/widget/RadioGroup.java29
-rw-r--r--core/java/android/widget/RelativeLayout.java220
-rw-r--r--core/java/android/widget/RemoteViews.java312
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java28
-rw-r--r--core/java/android/widget/ScrollView.java7
-rw-r--r--core/java/android/widget/Scroller.java101
-rw-r--r--core/java/android/widget/SearchView.java21
-rw-r--r--core/java/android/widget/SlidingDrawer.java5
-rw-r--r--core/java/android/widget/Spinner.java28
-rw-r--r--core/java/android/widget/StackView.java4
-rw-r--r--core/java/android/widget/Switch.java97
-rw-r--r--core/java/android/widget/TableLayout.java10
-rw-r--r--core/java/android/widget/TableRow.java17
-rw-r--r--core/java/android/widget/TextView.java168
-rw-r--r--core/java/android/widget/TimePicker.java4
-rw-r--r--core/java/android/widget/Toast.java18
-rw-r--r--core/java/android/widget/TwoLineListItem.java4
-rw-r--r--core/java/android/widget/VideoView.java14
-rw-r--r--core/java/android/widget/ViewAnimator.java15
-rw-r--r--core/java/android/widget/ZoomButtonsController.java2
-rw-r--r--core/java/android/widget/package.html17
-rw-r--r--core/java/com/android/internal/app/AlertController.java1
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl8
-rw-r--r--core/java/com/android/internal/app/LocalePicker.java38
-rw-r--r--core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java22
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java36
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetHost.aidl1
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetService.aidl4
-rw-r--r--core/java/com/android/internal/content/PackageHelper.java1
-rw-r--r--core/java/com/android/internal/content/PackageMonitor.java83
-rw-r--r--core/java/com/android/internal/net/LegacyVpnInfo.java25
-rw-r--r--core/java/com/android/internal/net/VpnConfig.java10
-rw-r--r--core/java/com/android/internal/net/VpnProfile.aidl19
-rw-r--r--core/java/com/android/internal/net/VpnProfile.java181
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java179
-rw-r--r--core/java/com/android/internal/os/HandlerCaller.java66
-rw-r--r--core/java/com/android/internal/os/SomeArgs.java95
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java17
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java15
-rw-r--r--core/java/com/android/internal/os/storage/ExternalStorageFormatter.java6
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl5
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl6
-rw-r--r--core/java/com/android/internal/statusbar/StatusBarIcon.java14
-rw-r--r--core/java/com/android/internal/statusbar/StatusBarNotification.java58
-rw-r--r--core/java/com/android/internal/util/DumpUtils.java57
-rw-r--r--core/java/com/android/internal/util/IndentingPrintWriter.java38
-rw-r--r--core/java/com/android/internal/util/JournaledFile.java9
-rw-r--r--core/java/com/android/internal/util/Preconditions.java12
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java26
-rw-r--r--core/java/com/android/internal/view/IInputMethodSession.aidl2
-rw-r--r--core/java/com/android/internal/view/RotationPolicy.java42
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItemView.java42
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuPresenter.java10
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuView.java73
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuItemView.java4
-rw-r--r--core/java/com/android/internal/widget/AbsActionBarView.java23
-rw-r--r--core/java/com/android/internal/widget/ActionBarContainer.java87
-rw-r--r--core/java/com/android/internal/widget/ActionBarContextView.java21
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java173
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java227
-rw-r--r--core/java/com/android/internal/widget/LockSettingsService.java33
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java84
-rw-r--r--core/java/com/android/internal/widget/ScrollingTabContainerView.java36
-rw-r--r--core/java/com/android/internal/widget/TextProgressBar.java2
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/GlowPadView.java42
-rw-r--r--core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java3
442 files changed, 28858 insertions, 22451 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 81ee192..b0bad07 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -323,7 +323,7 @@ public abstract class AccessibilityService extends Service {
public static final int GLOBAL_ACTION_HOME = 2;
/**
- * Action to open the recents.
+ * Action to open the recent apps.
*/
public static final int GLOBAL_ACTION_RECENTS = 3;
@@ -332,6 +332,11 @@ public abstract class AccessibilityService extends Service {
*/
public static final int GLOBAL_ACTION_NOTIFICATIONS = 4;
+ /**
+ * Action to open the quick settings.
+ */
+ public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5;
+
private static final String LOG_TAG = "AccessibilityService";
interface Callbacks {
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 10ea0fe..75a4f83 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -85,6 +85,11 @@ public class AccessibilityServiceInfo implements Parcelable {
public static final int FEEDBACK_GENERIC = 0x0000010;
/**
+ * Denotes braille feedback.
+ */
+ public static final int FEEDBACK_BRAILLE = 0x0000020;
+
+ /**
* Mask for all feedback types.
*
* @see #FEEDBACK_SPOKEN
@@ -92,6 +97,7 @@ public class AccessibilityServiceInfo implements Parcelable {
* @see #FEEDBACK_AUDIBLE
* @see #FEEDBACK_VISUAL
* @see #FEEDBACK_GENERIC
+ * @see #FEEDBACK_BRAILLE
*/
public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF;
@@ -186,6 +192,7 @@ public class AccessibilityServiceInfo implements Parcelable {
* @see #FEEDBACK_HAPTIC
* @see #FEEDBACK_SPOKEN
* @see #FEEDBACK_VISUAL
+ * @see #FEEDBACK_BRAILLE
*/
public int feedbackType;
@@ -591,6 +598,12 @@ public class AccessibilityServiceInfo implements Parcelable {
}
builder.append("FEEDBACK_VISUAL");
break;
+ case FEEDBACK_BRAILLE:
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append("FEEDBACK_BRAILLE");
+ break;
}
}
builder.append("]");
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 22e454f..fc569e0 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -50,7 +50,8 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.UserId;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -92,6 +93,7 @@ public class AccountManagerService
private final Context mContext;
private final PackageManager mPackageManager;
+ private UserManager mUserManager;
private HandlerThread mMessageThread;
private final MessageHandler mMessageHandler;
@@ -245,6 +247,13 @@ public class AccountManagerService
initUser(0);
}
+ private UserManager getUserManager() {
+ if (mUserManager == null) {
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+ return mUserManager;
+ }
+
private UserAccounts initUser(int userId) {
synchronized (mUsers) {
UserAccounts accounts = mUsers.get(userId);
@@ -345,7 +354,7 @@ public class AccountManagerService
}
private UserAccounts getUserAccountsForCaller() {
- return getUserAccounts(UserId.getCallingUserId());
+ return getUserAccounts(UserHandle.getCallingUserId());
}
protected UserAccounts getUserAccounts(int userId) {
@@ -360,7 +369,7 @@ public class AccountManagerService
}
private void onUserRemoved(Intent intent) {
- int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1);
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userId < 1) return;
UserAccounts accounts;
@@ -382,12 +391,7 @@ public class AccountManagerService
}
private List<UserInfo> getAllUsers() {
- try {
- return AppGlobals.getPackageManager().getUsers();
- } catch (RemoteException re) {
- // Local to system process, shouldn't happen
- }
- return null;
+ return getUserManager().getUsers();
}
public void onServiceChanged(AuthenticatorDescription desc, boolean removed) {
@@ -643,16 +647,17 @@ public class AccountManagerService
if (response == null) throw new IllegalArgumentException("response is null");
if (account == null) throw new IllegalArgumentException("account is null");
checkManageAccountsPermission();
+ UserHandle user = Binder.getCallingUserHandle();
UserAccounts accounts = getUserAccountsForCaller();
long identityToken = clearCallingIdentity();
- cancelNotification(getSigninRequiredNotificationId(accounts, account));
+ cancelNotification(getSigninRequiredNotificationId(accounts, account), user);
synchronized(accounts.credentialsPermissionNotificationIds) {
for (Pair<Pair<Account, String>, Integer> pair:
accounts.credentialsPermissionNotificationIds.keySet()) {
if (account.equals(pair.first.first)) {
int id = accounts.credentialsPermissionNotificationIds.get(pair);
- cancelNotification(id);
+ cancelNotification(id, user);
}
}
}
@@ -785,7 +790,8 @@ public class AccountManagerService
if (account == null || type == null) {
return false;
}
- cancelNotification(getSigninRequiredNotificationId(accounts, account));
+ cancelNotification(getSigninRequiredNotificationId(accounts, account),
+ new UserHandle(accounts.userId));
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
db.beginTransaction();
@@ -896,7 +902,7 @@ public class AccountManagerService
private void sendAccountsChangedBroadcast(int userId) {
Log.i(TAG, "the accounts changed, sending broadcast of "
+ ACCOUNTS_CHANGED_INTENT.getAction());
- mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT, userId);
+ mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
}
public void clearPassword(Account account) {
@@ -1000,7 +1006,7 @@ public class AccountManagerService
if (callingUid != android.os.Process.SYSTEM_UID) {
throw new SecurityException("can only call from system");
}
- UserAccounts accounts = getUserAccounts(UserId.getUserId(callingUid));
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
long identityToken = clearCallingIdentity();
try {
new Session(accounts, response, accountType, false,
@@ -1048,7 +1054,7 @@ public class AccountManagerService
if (account == null) throw new IllegalArgumentException("account is null");
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
- UserAccounts accounts = getUserAccountsForCaller();
+ final UserAccounts accounts = getUserAccountsForCaller();
AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
mAuthenticatorCache.getServiceInfo(
AuthenticatorDescription.newKey(account.type));
@@ -1137,7 +1143,7 @@ public class AccountManagerService
if (intent != null && notifyOnAuthFailure && !customTokens) {
doNotification(mAccounts,
account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE),
- intent);
+ intent, accounts.userId);
}
}
super.onResult(result);
@@ -1148,7 +1154,8 @@ public class AccountManagerService
}
}
- private void createNoCredentialsPermissionNotification(Account account, Intent intent) {
+ private void createNoCredentialsPermissionNotification(Account account, Intent intent,
+ int userId) {
int uid = intent.getIntExtra(
GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1);
String authTokenType = intent.getStringExtra(
@@ -1168,10 +1175,12 @@ public class AccountManagerService
title = titleAndSubtitle.substring(0, index);
subtitle = titleAndSubtitle.substring(index + 1);
}
- n.setLatestEventInfo(mContext,
- title, subtitle,
- PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
- installNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), n);
+ UserHandle user = new UserHandle(userId);
+ n.setLatestEventInfo(mContext, title, subtitle,
+ PendingIntent.getActivityAsUser(mContext, 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null, user));
+ installNotification(getCredentialPermissionNotificationId(
+ account, authTokenType, uid), n, user);
}
String getAccountLabel(String accountType) {
@@ -1218,7 +1227,7 @@ public class AccountManagerService
private Integer getCredentialPermissionNotificationId(Account account, String authTokenType,
int uid) {
Integer id;
- UserAccounts accounts = getUserAccounts(UserId.getUserId(uid));
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
synchronized (accounts.credentialsPermissionNotificationIds) {
final Pair<Pair<Account, String>, Integer> key =
new Pair<Pair<Account, String>, Integer>(
@@ -1757,7 +1766,8 @@ public class AccountManagerService
String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
Account account = new Account(accountName, accountType);
- cancelNotification(getSigninRequiredNotificationId(mAccounts, account));
+ cancelNotification(getSigninRequiredNotificationId(mAccounts, account),
+ new UserHandle(mAccounts.userId));
}
}
IAccountManagerResponse response;
@@ -1875,7 +1885,7 @@ public class AccountManagerService
private static String getDatabaseName(int userId) {
File systemDir = Environment.getSystemSecureDirectory();
- File databaseFile = new File(systemDir, "users/" + userId + "/" + DATABASE_NAME);
+ File databaseFile = new File(Environment.getUserSystemDirectory(userId), DATABASE_NAME);
if (userId == 0) {
// Migrate old file, if it exists, to the new location.
// Make sure the new file doesn't already exist. A dummy file could have been
@@ -1884,7 +1894,7 @@ public class AccountManagerService
File oldFile = new File(systemDir, DATABASE_NAME);
if (oldFile.exists() && !databaseFile.exists()) {
// Check for use directory; create if it doesn't exist, else renameTo will fail
- File userDir = new File(systemDir, "users/" + userId);
+ File userDir = Environment.getUserSystemDirectory(userId);
if (!userDir.exists()) {
if (!userDir.mkdirs()) {
throw new IllegalStateException("User dir cannot be created: " + userDir);
@@ -2079,7 +2089,7 @@ public class AccountManagerService
}
private void doNotification(UserAccounts accounts, Account account, CharSequence message,
- Intent intent) {
+ Intent intent, int userId) {
long identityToken = clearCallingIdentity();
try {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -2089,35 +2099,38 @@ public class AccountManagerService
if (intent.getComponent() != null &&
GrantCredentialsPermissionActivity.class.getName().equals(
intent.getComponent().getClassName())) {
- createNoCredentialsPermissionNotification(account, intent);
+ createNoCredentialsPermissionNotification(account, intent, userId);
} else {
final Integer notificationId = getSigninRequiredNotificationId(accounts, account);
intent.addCategory(String.valueOf(notificationId));
Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
0 /* when */);
+ UserHandle user = new UserHandle(userId);
final String notificationTitleFormat =
mContext.getText(R.string.notification_title).toString();
n.setLatestEventInfo(mContext,
String.format(notificationTitleFormat, account.name),
- message, PendingIntent.getActivity(
- mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
- installNotification(notificationId, n);
+ message, PendingIntent.getActivityAsUser(
+ mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT,
+ null, user));
+ installNotification(notificationId, n, user);
}
} finally {
restoreCallingIdentity(identityToken);
}
}
- protected void installNotification(final int notificationId, final Notification n) {
+ protected void installNotification(final int notificationId, final Notification n,
+ UserHandle user) {
((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
- .notify(notificationId, n);
+ .notifyAsUser(null, notificationId, n, user);
}
- protected void cancelNotification(int id) {
+ protected void cancelNotification(int id, UserHandle user) {
long identityToken = clearCallingIdentity();
try {
((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
- .cancel(id);
+ .cancelAsUser(null, id, user);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -2265,7 +2278,7 @@ public class AccountManagerService
Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception());
return;
}
- UserAccounts accounts = getUserAccounts(UserId.getUserId(uid));
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
db.beginTransaction();
@@ -2282,7 +2295,8 @@ public class AccountManagerService
} finally {
db.endTransaction();
}
- cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid));
+ cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
+ new UserHandle(accounts.userId));
}
}
@@ -2299,7 +2313,7 @@ public class AccountManagerService
Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception());
return;
}
- UserAccounts accounts = getUserAccounts(UserId.getUserId(uid));
+ UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
db.beginTransaction();
@@ -2316,7 +2330,8 @@ public class AccountManagerService
} finally {
db.endTransaction();
}
- cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid));
+ cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
+ new UserHandle(accounts.userId));
}
}
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 6b3b7fd..5358bc7 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -15,6 +15,8 @@
*/
package android.accounts;
+import com.google.android.collect.Sets;
+
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
@@ -105,6 +107,13 @@ public class ChooseTypeAndAccountActivity extends Activity
private static final int SELECTED_ITEM_NONE = -1;
+ private Set<Account> mSetOfAllowableAccounts;
+ private Set<String> mSetOfRelevantAccountTypes;
+ private String mSelectedAccountName = null;
+ private boolean mSelectedAddNewAccount = false;
+ private boolean mAlwaysPromptForAccount = false;
+ private String mDescriptionOverride;
+
private ArrayList<Account> mAccounts;
private int mPendingRequest = REQUEST_NULL;
private Parcelable[] mExistingAccounts = null;
@@ -120,22 +129,18 @@ public class ChooseTypeAndAccountActivity extends Activity
}
// save some items we use frequently
- final AccountManager accountManager = AccountManager.get(this);
final Intent intent = getIntent();
- String selectedAccountName = null;
- boolean selectedAddNewAccount = false;
-
if (savedInstanceState != null) {
mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST);
mExistingAccounts =
savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS);
// Makes sure that any user selection is preserved across orientation changes.
- selectedAccountName = savedInstanceState.getString(
+ mSelectedAccountName = savedInstanceState.getString(
KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME);
- selectedAddNewAccount = savedInstanceState.getBoolean(
+ mSelectedAddNewAccount = savedInstanceState.getBoolean(
KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
} else {
mPendingRequest = REQUEST_NULL;
@@ -144,85 +149,38 @@ public class ChooseTypeAndAccountActivity extends Activity
// show is as pre-selected.
Account selectedAccount = (Account) intent.getParcelableExtra(EXTRA_SELECTED_ACCOUNT);
if (selectedAccount != null) {
- selectedAccountName = selectedAccount.name;
+ mSelectedAccountName = selectedAccount.name;
}
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "selected account name is " + selectedAccountName);
+ Log.v(TAG, "selected account name is " + mSelectedAccountName);
}
- // build an efficiently queryable map of account types to authenticator descriptions
- final HashMap<String, AuthenticatorDescription> typeToAuthDescription =
- new HashMap<String, AuthenticatorDescription>();
- for(AuthenticatorDescription desc : accountManager.getAuthenticatorTypes()) {
- typeToAuthDescription.put(desc.type, desc);
- }
-
- // Read the validAccounts, if present, and add them to the setOfAllowableAccounts
- Set<Account> setOfAllowableAccounts = null;
- final ArrayList<Parcelable> validAccounts =
- intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST);
- if (validAccounts != null) {
- setOfAllowableAccounts = new HashSet<Account>(validAccounts.size());
- for (Parcelable parcelable : validAccounts) {
- setOfAllowableAccounts.add((Account)parcelable);
- }
- }
- // An account type is relevant iff it is allowed by the caller and supported by the account
- // manager.
- Set<String> setOfRelevantAccountTypes = null;
- final String[] allowedAccountTypes =
- intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY);
- if (allowedAccountTypes != null) {
-
- setOfRelevantAccountTypes = new HashSet<String>(allowedAccountTypes.length);
- Set<String> setOfAllowedAccountTypes = new HashSet<String>(allowedAccountTypes.length);
- for (String type : allowedAccountTypes) {
- setOfAllowedAccountTypes.add(type);
- }
-
- AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes();
- Set<String> supportedAccountTypes = new HashSet<String>(descs.length);
- for (AuthenticatorDescription desc : descs) {
- supportedAccountTypes.add(desc.type);
- }
+ mSetOfAllowableAccounts = getAllowableAccountSet(intent);
+ mSetOfRelevantAccountTypes = getReleventAccountTypes(intent);
+ mAlwaysPromptForAccount = intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false);
+ mDescriptionOverride = intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
+ }
- for (String acctType : setOfAllowedAccountTypes) {
- if (supportedAccountTypes.contains(acctType)) {
- setOfRelevantAccountTypes.add(acctType);
- }
- }
- }
+ @Override
+ protected void onResume() {
+ super.onResume();
+ final AccountManager accountManager = AccountManager.get(this);
- // Create a list of AccountInfo objects for each account that is allowable. Filter out
- // accounts that don't match the allowable types, if provided, or that don't match the
- // allowable accounts, if provided.
- final Account[] accounts = accountManager.getAccounts();
- mAccounts = new ArrayList<Account>(accounts.length);
- mSelectedItemIndex = SELECTED_ITEM_NONE;
- for (Account account : accounts) {
- if (setOfAllowableAccounts != null
- && !setOfAllowableAccounts.contains(account)) {
- continue;
- }
- if (setOfRelevantAccountTypes != null
- && !setOfRelevantAccountTypes.contains(account.type)) {
- continue;
- }
- if (account.name.equals(selectedAccountName)) {
- mSelectedItemIndex = mAccounts.size();
- }
- mAccounts.add(account);
- }
+ mAccounts = getAcceptableAccountChoices(accountManager);
+ // In cases where the activity does not need to show an account picker, cut the chase
+ // and return the result directly. Eg:
+ // Single account -> select it directly
+ // No account -> launch add account activity directly
if (mPendingRequest == REQUEST_NULL) {
// 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 (setOfRelevantAccountTypes.size() == 1) {
- runAddAccountForAuthenticator(setOfRelevantAccountTypes.iterator().next());
+ if (mSetOfRelevantAccountTypes.size() == 1) {
+ runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next());
} else {
startChooseAccountTypeActivity();
}
@@ -230,61 +188,22 @@ public class ChooseTypeAndAccountActivity extends Activity
}
// if there is only one allowable account return it
- if (!intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false)
- && mAccounts.size() == 1) {
+ if (!mAlwaysPromptForAccount && mAccounts.size() == 1) {
Account account = mAccounts.get(0);
setResultAndFinish(account.name, account.type);
return;
}
}
+ String[] listItems = getListOfDisplayableOptions(mAccounts);
+ mSelectedItemIndex = getItemIndexToSelect(
+ mAccounts, mSelectedAccountName, mSelectedAddNewAccount);
+
// Cannot set content view until we know that mPendingRequest is not null, otherwise
// would cause screen flicker.
setContentView(R.layout.choose_type_and_account);
-
- // Override the description text if supplied
- final String descriptionOverride =
- intent.getStringExtra(EXTRA_DESCRIPTION_TEXT_OVERRIDE);
- TextView descriptionView = (TextView) findViewById(R.id.description);
- if (!TextUtils.isEmpty(descriptionOverride)) {
- descriptionView.setText(descriptionOverride);
- } else {
- descriptionView.setVisibility(View.GONE);
- }
-
- // List of options includes all accounts found together with "Add new account" as the
- // last item in the list.
- String[] listItems = new String[mAccounts.size() + 1];
- for (int i = 0; i < mAccounts.size(); i++) {
- listItems[i] = mAccounts.get(i).name;
- }
- listItems[mAccounts.size()] = getResources().getString(
- R.string.add_account_button_label);
-
- ListView list = (ListView) findViewById(android.R.id.list);
- list.setAdapter(new ArrayAdapter<String>(this,
- android.R.layout.simple_list_item_single_choice, listItems));
- list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
- list.setItemsCanFocus(false);
- list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
- mSelectedItemIndex = position;
- mOkButton.setEnabled(true);
- }
- });
-
- // If "Add account" option was previously selected by user, preserve it across
- // orientation changes.
- if (selectedAddNewAccount) {
- mSelectedItemIndex = mAccounts.size();
- }
- if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
- list.setItemChecked(mSelectedItemIndex, true);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "List item " + mSelectedItemIndex + " should be selected");
- }
- }
+ overrideDescriptionIfSupplied(mDescriptionOverride);
+ populateUIAccountList(listItems);
// Only enable "OK" button if something has been selected.
mOkButton = (Button) findViewById(android.R.id.button2);
@@ -480,4 +399,137 @@ public class ChooseTypeAndAccountActivity extends Activity
startActivityForResult(intent, REQUEST_CHOOSE_TYPE);
mPendingRequest = REQUEST_CHOOSE_TYPE;
}
+
+ /**
+ * @return a value between 0 (inclusive) and accounts.size() (inclusive) or SELECTED_ITEM_NONE.
+ * An index value of accounts.size() indicates 'Add account' option.
+ */
+ private int getItemIndexToSelect(ArrayList<Account> accounts, String selectedAccountName,
+ boolean selectedAddNewAccount) {
+ // If "Add account" option was previously selected by user, preserve it across
+ // orientation changes.
+ if (selectedAddNewAccount) {
+ return accounts.size();
+ }
+ // search for the selected account name if present
+ for (int i = 0; i < accounts.size(); i++) {
+ if (accounts.get(i).name.equals(selectedAccountName)) {
+ return i;
+ }
+ }
+ // no account selected.
+ return SELECTED_ITEM_NONE;
+ }
+
+ private String[] getListOfDisplayableOptions(ArrayList<Account> accounts) {
+ // List of options includes all accounts found together with "Add new account" as the
+ // last item in the list.
+ String[] listItems = new String[accounts.size() + 1];
+ for (int i = 0; i < accounts.size(); i++) {
+ listItems[i] = accounts.get(i).name;
+ }
+ listItems[accounts.size()] = getResources().getString(
+ R.string.add_account_button_label);
+ return listItems;
+ }
+
+ /**
+ * Create a list of Account objects for each account that is acceptable. Filter out
+ * accounts that don't match the allowable types, if provided, or that don't match the
+ * allowable accounts, if provided.
+ */
+ private ArrayList<Account> getAcceptableAccountChoices(AccountManager accountManager) {
+ final Account[] accounts = accountManager.getAccounts();
+ ArrayList<Account> accountsToPopulate = new ArrayList<Account>(accounts.length);
+ for (Account account : accounts) {
+ if (mSetOfAllowableAccounts != null
+ && !mSetOfAllowableAccounts.contains(account)) {
+ continue;
+ }
+ if (mSetOfRelevantAccountTypes != null
+ && !mSetOfRelevantAccountTypes.contains(account.type)) {
+ continue;
+ }
+ accountsToPopulate.add(account);
+ }
+ return accountsToPopulate;
+ }
+
+ /**
+ * Return a set of account types speficied by the intent as well as supported by the
+ * AccountManager.
+ */
+ private Set<String> getReleventAccountTypes(final Intent intent) {
+ // An account type is relevant iff it is allowed by the caller and supported by the account
+ // manager.
+ Set<String> setOfRelevantAccountTypes = null;
+ final String[] allowedAccountTypes =
+ intent.getStringArrayExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY);
+ if (allowedAccountTypes != null) {
+ setOfRelevantAccountTypes = Sets.newHashSet(allowedAccountTypes);
+ AuthenticatorDescription[] descs = AccountManager.get(this).getAuthenticatorTypes();
+ Set<String> supportedAccountTypes = new HashSet<String>(descs.length);
+ for (AuthenticatorDescription desc : descs) {
+ supportedAccountTypes.add(desc.type);
+ }
+ setOfRelevantAccountTypes.retainAll(supportedAccountTypes);
+ }
+ return setOfRelevantAccountTypes;
+ }
+
+ /**
+ * Returns a set of whitelisted accounts given by the intent or null if none specified by the
+ * intent.
+ */
+ private Set<Account> getAllowableAccountSet(final Intent intent) {
+ Set<Account> setOfAllowableAccounts = null;
+ final ArrayList<Parcelable> validAccounts =
+ intent.getParcelableArrayListExtra(EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST);
+ if (validAccounts != null) {
+ setOfAllowableAccounts = new HashSet<Account>(validAccounts.size());
+ for (Parcelable parcelable : validAccounts) {
+ setOfAllowableAccounts.add((Account)parcelable);
+ }
+ }
+ return setOfAllowableAccounts;
+ }
+
+ /**
+ * Overrides the description text view for the picker activity if specified by the intent.
+ * If not specified then makes the description invisible.
+ */
+ private void overrideDescriptionIfSupplied(String descriptionOverride) {
+ TextView descriptionView = (TextView) findViewById(R.id.description);
+ if (!TextUtils.isEmpty(descriptionOverride)) {
+ descriptionView.setText(descriptionOverride);
+ } else {
+ descriptionView.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Populates the UI ListView with the given list of items and selects an item
+ * based on {@code mSelectedItemIndex} member variable.
+ */
+ private final void populateUIAccountList(String[] listItems) {
+ ListView list = (ListView) findViewById(android.R.id.list);
+ list.setAdapter(new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_single_choice, listItems));
+ list.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ list.setItemsCanFocus(false);
+ list.setOnItemClickListener(
+ new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+ mSelectedItemIndex = position;
+ mOkButton.setEnabled(true);
+ }
+ });
+ if (mSelectedItemIndex != SELECTED_ITEM_NONE) {
+ list.setItemChecked(mSelectedItemIndex, true);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "List item " + mSelectedItemIndex + " should be selected");
+ }
+ }
+ }
}
diff --git a/core/java/android/animation/ArgbEvaluator.java b/core/java/android/animation/ArgbEvaluator.java
index c3875be..717a3d9 100644
--- a/core/java/android/animation/ArgbEvaluator.java
+++ b/core/java/android/animation/ArgbEvaluator.java
@@ -40,13 +40,13 @@ public class ArgbEvaluator implements TypeEvaluator {
*/
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
- int startA = (startInt >> 24);
+ int startA = (startInt >> 24) & 0xff;
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
- int endA = (endInt >> 24);
+ int endA = (endInt >> 24) & 0xff;
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;
diff --git a/core/java/android/animation/Keyframe.java b/core/java/android/animation/Keyframe.java
index e98719a..dc8538f 100644
--- a/core/java/android/animation/Keyframe.java
+++ b/core/java/android/animation/Keyframe.java
@@ -261,7 +261,7 @@ public abstract class Keyframe implements Cloneable {
@Override
public ObjectKeyframe clone() {
- ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), mValue);
+ ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), mHasValue ? mValue : null);
kfClone.setInterpolator(getInterpolator());
return kfClone;
}
@@ -306,7 +306,9 @@ public abstract class Keyframe implements Cloneable {
@Override
public IntKeyframe clone() {
- IntKeyframe kfClone = new IntKeyframe(getFraction(), mValue);
+ IntKeyframe kfClone = mHasValue ?
+ new IntKeyframe(getFraction(), mValue) :
+ new IntKeyframe(getFraction());
kfClone.setInterpolator(getInterpolator());
return kfClone;
}
@@ -350,7 +352,9 @@ public abstract class Keyframe implements Cloneable {
@Override
public FloatKeyframe clone() {
- FloatKeyframe kfClone = new FloatKeyframe(getFraction(), mValue);
+ FloatKeyframe kfClone = mHasValue ?
+ new FloatKeyframe(getFraction(), mValue) :
+ new FloatKeyframe(getFraction());
kfClone.setInterpolator(getInterpolator());
return kfClone;
}
diff --git a/core/java/android/animation/TimeAnimator.java b/core/java/android/animation/TimeAnimator.java
index 088d20d..f9aa00e 100644
--- a/core/java/android/animation/TimeAnimator.java
+++ b/core/java/android/animation/TimeAnimator.java
@@ -13,6 +13,12 @@ public class TimeAnimator extends ValueAnimator {
private long mPreviousTime = -1;
@Override
+ public void start() {
+ mPreviousTime = -1;
+ super.start();
+ }
+
+ @Override
boolean animationFrame(long currentTime) {
if (mListener != null) {
long totalTime = currentTime - mStartTime;
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index f3a442a..f7460c4 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -232,6 +232,13 @@ public class ValueAnimator extends Animator {
}
/**
+ * @hide
+ */
+ public static float getDurationScale() {
+ return sDurationScale;
+ }
+
+ /**
* Creates a new ValueAnimator object. This default constructor is primarily for
* use internally; the factory methods which take parameters are more generally
* useful.
@@ -529,6 +536,9 @@ public class ValueAnimator extends Animator {
// The per-thread list of all active animations
private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
+ // Used in doAnimationFrame() to avoid concurrent modifications of mAnimations
+ private final ArrayList<ValueAnimator> mTmpAnimations = new ArrayList<ValueAnimator>();
+
// The per-thread set of animations to be started on the next animation frame
private final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();
@@ -598,28 +608,18 @@ public class ValueAnimator extends Animator {
// Now process all active animations. The return value from animationFrame()
// tells the handler whether it should now be ended
int numAnims = mAnimations.size();
- int i = 0;
- while (i < numAnims) {
- ValueAnimator anim = mAnimations.get(i);
- if (anim.doAnimationFrame(frameTime)) {
+ for (int i = 0; i < numAnims; ++i) {
+ mTmpAnimations.add(mAnimations.get(i));
+ }
+ for (int i = 0; i < numAnims; ++i) {
+ ValueAnimator anim = mTmpAnimations.get(i);
+ if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
mEndingAnims.add(anim);
}
- if (mAnimations.size() == numAnims) {
- ++i;
- } else {
- // An animation might be canceled or ended by client code
- // during the animation frame. Check to see if this happened by
- // seeing whether the current index is the same as it was before
- // calling animationFrame(). Another approach would be to copy
- // animations to a temporary list and process that list instead,
- // but that entails garbage and processing overhead that would
- // be nice to avoid.
- --numAnims;
- mEndingAnims.remove(anim);
- }
}
+ mTmpAnimations.clear();
if (mEndingAnims.size() > 0) {
- for (i = 0; i < mEndingAnims.size(); ++i) {
+ for (int i = 0; i < mEndingAnims.size(); ++i) {
mEndingAnims.get(i).endAnimation(this);
}
mEndingAnims.clear();
@@ -958,13 +958,7 @@ public class ValueAnimator extends Animator {
} else if (!mInitialized) {
initAnimation();
}
- // The final value set on the target varies, depending on whether the animation
- // was supposed to repeat an odd number of times
- if (mRepeatCount > 0 && (mRepeatCount & 0x01) == 1) {
- animateValue(0f);
- } else {
- animateValue(1f);
- }
+ animateValue(mPlayingBackwards ? 0f : 1f);
endAnimation(handler);
}
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index cff16ff..2337790 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -918,6 +918,8 @@ public abstract class ActionBar {
@ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
@ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"),
@ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"),
+ @ViewDebug.IntToString(from = Gravity.START, to = "START"),
+ @ViewDebug.IntToString(from = Gravity.END, to = "END"),
@ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"),
@ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"),
@ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
@@ -925,7 +927,7 @@ public abstract class ActionBar {
@ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"),
@ViewDebug.IntToString(from = Gravity.FILL, to = "FILL")
})
- public int gravity = -1;
+ public int gravity = Gravity.NO_GRAVITY;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
@@ -933,13 +935,14 @@ public abstract class ActionBar {
TypedArray a = c.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.ActionBar_LayoutParams);
gravity = a.getInt(
- com.android.internal.R.styleable.ActionBar_LayoutParams_layout_gravity, -1);
+ com.android.internal.R.styleable.ActionBar_LayoutParams_layout_gravity,
+ Gravity.NO_GRAVITY);
a.recycle();
}
public LayoutParams(int width, int height) {
super(width, height);
- this.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT;
+ this.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
}
public LayoutParams(int width, int height, int gravity) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f20fd33..7606d5e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -48,6 +48,7 @@ import android.os.Looper;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.StrictMode;
+import android.os.UserHandle;
import android.text.Selection;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -74,7 +75,7 @@ import android.view.ViewGroup.LayoutParams;
import android.view.ViewManager;
import android.view.Window;
import android.view.WindowManager;
-import android.view.WindowManagerImpl;
+import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
@@ -652,8 +653,9 @@ public class Activity extends ContextThemeWrapper
/** Start of user-defined activity results. */
public static final int RESULT_FIRST_USER = 1;
+ static final String FRAGMENTS_TAG = "android:fragments";
+
private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState";
- private static final String FRAGMENTS_TAG = "android:fragments";
private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds";
private static final String SAVED_DIALOGS_TAG = "android:savedDialogs";
private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_";
@@ -696,7 +698,7 @@ public class Activity extends ContextThemeWrapper
Object activity;
HashMap<String, Object> children;
ArrayList<Fragment> fragments;
- SparseArray<LoaderManagerImpl> loaders;
+ HashMap<String, LoaderManagerImpl> loaders;
}
/* package */ NonConfigurationInstances mLastNonConfigurationInstances;
@@ -714,8 +716,14 @@ public class Activity extends ContextThemeWrapper
private int mTitleColor = 0;
final FragmentManagerImpl mFragments = new FragmentManagerImpl();
+ final FragmentContainer mContainer = new FragmentContainer() {
+ @Override
+ public View findViewById(int id) {
+ return Activity.this.findViewById(id);
+ }
+ };
- SparseArray<LoaderManagerImpl> mAllLoaderManagers;
+ HashMap<String, LoaderManagerImpl> mAllLoaderManagers;
LoaderManagerImpl mLoaderManager;
private static final class ManagedCursor {
@@ -743,6 +751,7 @@ public class Activity extends ContextThemeWrapper
protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused};
+ @SuppressWarnings("unused")
private final Object mInstanceTracker = StrictMode.trackActivity(this);
private Thread mUiThread;
@@ -807,19 +816,19 @@ public class Activity extends ContextThemeWrapper
return mLoaderManager;
}
mCheckedForLoaderManager = true;
- mLoaderManager = getLoaderManager(-1, mLoadersStarted, true);
+ mLoaderManager = getLoaderManager(null, mLoadersStarted, true);
return mLoaderManager;
}
- LoaderManagerImpl getLoaderManager(int index, boolean started, boolean create) {
+ LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
if (mAllLoaderManagers == null) {
- mAllLoaderManagers = new SparseArray<LoaderManagerImpl>();
+ mAllLoaderManagers = new HashMap<String, LoaderManagerImpl>();
}
- LoaderManagerImpl lm = mAllLoaderManagers.get(index);
+ LoaderManagerImpl lm = mAllLoaderManagers.get(who);
if (lm == null) {
if (create) {
- lm = new LoaderManagerImpl(this, started);
- mAllLoaderManagers.put(index, lm);
+ lm = new LoaderManagerImpl(who, this, started);
+ mAllLoaderManagers.put(who, lm);
}
} else {
lm.updateActivity(this);
@@ -1024,7 +1033,7 @@ public class Activity extends ContextThemeWrapper
if (mLoaderManager != null) {
mLoaderManager.doStart();
} else if (!mCheckedForLoaderManager) {
- mLoaderManager = getLoaderManager(-1, mLoadersStarted, false);
+ mLoaderManager = getLoaderManager(null, mLoadersStarted, false);
}
mCheckedForLoaderManager = true;
}
@@ -1600,13 +1609,17 @@ public class Activity extends ContextThemeWrapper
if (mAllLoaderManagers != null) {
// prune out any loader managers that were already stopped and so
// have nothing useful to retain.
- for (int i=mAllLoaderManagers.size()-1; i>=0; i--) {
- LoaderManagerImpl lm = mAllLoaderManagers.valueAt(i);
- if (lm.mRetaining) {
- retainLoaders = true;
- } else {
- lm.doDestroy();
- mAllLoaderManagers.removeAt(i);
+ LoaderManagerImpl loaders[] = new LoaderManagerImpl[mAllLoaderManagers.size()];
+ mAllLoaderManagers.values().toArray(loaders);
+ if (loaders != null) {
+ for (int i=0; i<loaders.length; i++) {
+ LoaderManagerImpl lm = loaders[i];
+ if (lm.mRetaining) {
+ retainLoaders = true;
+ } else {
+ lm.doDestroy();
+ mAllLoaderManagers.remove(lm.mWho);
+ }
}
}
}
@@ -1642,13 +1655,13 @@ public class Activity extends ContextThemeWrapper
return mFragments;
}
- void invalidateFragmentIndex(int index) {
+ void invalidateFragment(String who) {
//Log.v(TAG, "invalidateFragmentIndex: index=" + index);
if (mAllLoaderManagers != null) {
- LoaderManagerImpl lm = mAllLoaderManagers.get(index);
+ LoaderManagerImpl lm = mAllLoaderManagers.get(who);
if (lm != null && !lm.mRetaining) {
lm.doDestroy();
- mAllLoaderManagers.remove(index);
+ mAllLoaderManagers.remove(who);
}
}
}
@@ -2708,7 +2721,12 @@ public class Activity extends ContextThemeWrapper
// metadata is available.
Intent upIntent = getParentActivityIntent();
if (upIntent != null) {
- if (shouldUpRecreateTask(upIntent)) {
+ if (mActivityInfo.taskAffinity == null) {
+ // Activities with a null affinity are special; they really shouldn't
+ // specify a parent activity intent in the first place. Just finish
+ // the current activity and call it a day.
+ finish();
+ } else if (shouldUpRecreateTask(upIntent)) {
TaskStackBuilder b = TaskStackBuilder.create(this);
onCreateNavigateUpTaskStack(b);
onPrepareNavigateUpTaskStack(b);
@@ -3379,6 +3397,31 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @hide Implement to provide correct calling token.
+ */
+ public void startActivityAsUser(Intent intent, UserHandle user) {
+ startActivityAsUser(intent, null, user);
+ }
+
+ /**
+ * @hide Implement to provide correct calling token.
+ */
+ public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+ if (mParent != null) {
+ throw new RuntimeException("Called be called from a child");
+ }
+ Instrumentation.ActivityResult ar =
+ mInstrumentation.execStartActivity(
+ this, mMainThread.getApplicationThread(), mToken, this,
+ intent, -1, options, user);
+ if (ar != null) {
+ mMainThread.sendActivityResult(
+ mToken, mEmbeddedID, -1, ar.getResultCode(),
+ ar.getResultData());
+ }
+ }
+
+ /**
* Same as calling {@link #startIntentSenderForResult(IntentSender, int,
* Intent, int, int, int, Bundle)} with no options.
*
@@ -4247,7 +4290,8 @@ public class Activity extends ContextThemeWrapper
ActivityManagerNative.getDefault().getIntentSender(
ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName,
mParent == null ? mToken : mParent.mToken,
- mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null);
+ mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null,
+ UserHandle.myUserId());
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
// Empty
@@ -4707,6 +4751,10 @@ public class Activity extends ContextThemeWrapper
* @param args additional arguments to the dump request.
*/
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ dumpInner(prefix, fd, writer, args);
+ }
+
+ void dumpInner(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
writer.print(prefix); writer.print("Local Activity ");
writer.print(Integer.toHexString(System.identityHashCode(this)));
writer.println(" State:");
@@ -4728,6 +4776,29 @@ public class Activity extends ContextThemeWrapper
mLoaderManager.dump(prefix + " ", fd, writer, args);
}
mFragments.dump(prefix, fd, writer, args);
+ writer.print(prefix); writer.println("View Hierarchy:");
+ dumpViewHierarchy(prefix + " ", writer, getWindow().getDecorView());
+ }
+
+ private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) {
+ writer.print(prefix);
+ if (view == null) {
+ writer.println("null");
+ return;
+ }
+ writer.println(view.toString());
+ if (!(view instanceof ViewGroup)) {
+ return;
+ }
+ ViewGroup grp = (ViewGroup)view;
+ final int N = grp.getChildCount();
+ if (N <= 0) {
+ return;
+ }
+ prefix = prefix + " ";
+ for (int i=0; i<N; i++) {
+ dumpViewHierarchy(prefix, writer, grp.getChildAt(i));
+ }
}
/**
@@ -4939,7 +5010,21 @@ public class Activity extends ContextThemeWrapper
if (TextUtils.isEmpty(parentName)) {
return null;
}
- return new Intent().setClassName(this, parentName);
+
+ // If the parent itself has no parent, generate a main activity intent.
+ final ComponentName target = new ComponentName(this, parentName);
+ try {
+ final ActivityInfo parentInfo = getPackageManager().getActivityInfo(target, 0);
+ final String parentActivity = parentInfo.parentActivityName;
+ final Intent parentIntent = parentActivity == null
+ ? Intent.makeMainActivity(target)
+ : new Intent().setComponent(target);
+ return parentIntent;
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "getParentActivityIntent: bad parentActivityName '" + parentName +
+ "' in manifest");
+ return null;
+ }
}
// ------------------ Internal API ------------------
@@ -4964,7 +5049,7 @@ public class Activity extends ContextThemeWrapper
Configuration config) {
attachBaseContext(context);
- mFragments.attachActivity(this);
+ mFragments.attachActivity(this, mContainer, null);
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
@@ -4990,7 +5075,9 @@ public class Activity extends ContextThemeWrapper
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
- mWindow.setWindowManager(null, mToken, mComponent.flattenToString(),
+ mWindow.setWindowManager(
+ (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
+ mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
@@ -5023,10 +5110,14 @@ public class Activity extends ContextThemeWrapper
}
mFragments.dispatchStart();
if (mAllLoaderManagers != null) {
- for (int i=mAllLoaderManagers.size()-1; i>=0; i--) {
- LoaderManagerImpl lm = mAllLoaderManagers.valueAt(i);
- lm.finishRetain();
- lm.doReportStart();
+ LoaderManagerImpl loaders[] = new LoaderManagerImpl[mAllLoaderManagers.size()];
+ mAllLoaderManagers.values().toArray(loaders);
+ if (loaders != null) {
+ for (int i=0; i<loaders.length; i++) {
+ LoaderManagerImpl lm = loaders[i];
+ lm.finishRetain();
+ lm.doReportStart();
+ }
}
}
}
@@ -5037,7 +5128,7 @@ public class Activity extends ContextThemeWrapper
if (mStopped) {
mStopped = false;
if (mToken != null && mParent == null) {
- WindowManagerImpl.getDefault().setStoppedState(mToken, false);
+ WindowManagerGlobal.getInstance().setStoppedState(mToken, false);
}
synchronized (mManagedCursors) {
@@ -5137,7 +5228,7 @@ public class Activity extends ContextThemeWrapper
}
if (mToken != null && mParent == null) {
- WindowManagerImpl.getDefault().setStoppedState(mToken, true);
+ WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
}
mFragments.dispatchStop();
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 92b6f72..0eda6b4 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -27,9 +27,12 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
@@ -40,7 +43,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
-import android.os.UserId;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -209,6 +212,15 @@ public class ActivityManager {
*/
public static final int INTENT_SENDER_SERVICE = 4;
+ /** @hide User operation call: success! */
+ public static final int USER_OP_SUCCESS = 0;
+
+ /** @hide User operation call: given user id is not known. */
+ public static final int USER_OP_UNKNOWN_USER = -1;
+
+ /** @hide User operation call: given user id is the current user, can't be stopped. */
+ public static final int USER_OP_IS_CURRENT = -2;
+
/*package*/ ActivityManager(Context context, Handler handler) {
mContext = context;
mHandler = handler;
@@ -366,7 +378,7 @@ public class ActivityManager {
* (which tends to consume a lot more RAM).
* @hide
*/
- static public boolean isHighEndGfx(Display display) {
+ static public boolean isHighEndGfx() {
MemInfoReader reader = new MemInfoReader();
reader.readMemInfo();
if (reader.getTotalSize() >= (512*1024*1024)) {
@@ -374,6 +386,9 @@ public class ActivityManager {
// 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;
@@ -529,7 +544,36 @@ public class ActivityManager {
throws SecurityException {
try {
return ActivityManagerNative.getDefault().getRecentTasks(maxNum,
- flags);
+ flags, UserHandle.myUserId());
+ } catch (RemoteException e) {
+ // System dead, we will be dead too soon!
+ return null;
+ }
+ }
+
+ /**
+ * Same as {@link #getRecentTasks(int, int)} but returns the recent tasks for a
+ * specific user. It requires holding
+ * the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission.
+ * @param maxNum The maximum number of entries to return in the list. The
+ * actual number returned may be smaller, depending on how many tasks the
+ * user has started and the maximum number the system can remember.
+ * @param flags Information about what to return. May be any combination
+ * of {@link #RECENT_WITH_EXCLUDED} and {@link #RECENT_IGNORE_UNAVAILABLE}.
+ *
+ * @return Returns a list of RecentTaskInfo records describing each of
+ * the recent tasks.
+ *
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not hold the {@link android.Manifest.permission#GET_TASKS} or the
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permissions.
+ * @hide
+ */
+ public List<RecentTaskInfo> getRecentTasksForUser(int maxNum, int flags, int userId)
+ throws SecurityException {
+ try {
+ return ActivityManagerNative.getDefault().getRecentTasks(maxNum,
+ flags, userId);
} catch (RemoteException e) {
// System dead, we will be dead too soon!
return null;
@@ -820,7 +864,17 @@ public class ActivityManager {
return null;
}
}
-
+
+ /** @hide */
+ public Bitmap getTaskTopThumbnail(int id) throws SecurityException {
+ try {
+ return ActivityManagerNative.getDefault().getTaskTopThumbnail(id);
+ } catch (RemoteException e) {
+ // System dead, we will be dead too soon!
+ return null;
+ }
+ }
+
/**
* Flag for {@link #moveTaskToFront(int, int)}: also move the "home"
* activity along with the task, so it is positioned immediately behind
@@ -1182,7 +1236,7 @@ public class ActivityManager {
public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) {
try {
return ActivityManagerNative.getDefault().clearApplicationUserData(packageName,
- observer, Binder.getOrigCallingUser());
+ observer, UserHandle.myUserId());
} catch (RemoteException e) {
return false;
}
@@ -1346,6 +1400,13 @@ public class ActivityManager {
public static final int FLAG_PERSISTENT = 1<<1;
/**
+ * Constant for {@link #flags}: this process is associated with a
+ * persistent system app.
+ * @hide
+ */
+ public static final int FLAG_HAS_ACTIVITIES = 1<<2;
+
+ /**
* Flags of information. May be any of
* {@link #FLAG_CANT_SAVE_STATE}.
* @hide
@@ -1642,7 +1703,8 @@ public class ActivityManager {
*/
public void killBackgroundProcesses(String packageName) {
try {
- ActivityManagerNative.getDefault().killBackgroundProcesses(packageName);
+ ActivityManagerNative.getDefault().killBackgroundProcesses(packageName,
+ UserHandle.myUserId());
} catch (RemoteException e) {
}
}
@@ -1667,7 +1729,8 @@ public class ActivityManager {
*/
public void forceStopPackage(String packageName) {
try {
- ActivityManagerNative.getDefault().forceStopPackage(packageName);
+ ActivityManagerNative.getDefault().forceStopPackage(packageName,
+ UserHandle.myUserId());
} catch (RemoteException e) {
}
}
@@ -1814,12 +1877,12 @@ public class ActivityManager {
return PackageManager.PERMISSION_GRANTED;
}
// Isolated processes don't get any permissions.
- if (UserId.isIsolated(uid)) {
+ if (UserHandle.isIsolated(uid)) {
return PackageManager.PERMISSION_DENIED;
}
// If there is a uid that owns whatever is being accessed, it has
// blanket access to it regardless of the permissions it requires.
- if (owningUid >= 0 && UserId.isSameApp(uid, owningUid)) {
+ if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
return PackageManager.PERMISSION_GRANTED;
}
// If the target is not exported, then nobody else can get to it.
@@ -1840,6 +1903,43 @@ public class ActivityManager {
return PackageManager.PERMISSION_DENIED;
}
+ /** @hide */
+ public static int checkUidPermission(String permission, int uid) {
+ try {
+ return AppGlobals.getPackageManager()
+ .checkUidPermission(permission, uid);
+ } catch (RemoteException e) {
+ // Should never happen, but if it does... deny!
+ Slog.e(TAG, "PackageManager is dead?!?", e);
+ }
+ return PackageManager.PERMISSION_DENIED;
+ }
+
+ /** @hide */
+ public static int handleIncomingUser(int callingPid, int callingUid, int userId,
+ boolean allowAll, boolean requireFull, String name, String callerPackage) {
+ if (UserHandle.getUserId(callingUid) == userId) {
+ return userId;
+ }
+ try {
+ return ActivityManagerNative.getDefault().handleIncomingUser(callingPid,
+ callingUid, userId, allowAll, requireFull, name, callerPackage);
+ } catch (RemoteException e) {
+ throw new SecurityException("Failed calling activity manager", e);
+ }
+ }
+
+ /** @hide */
+ public static int getCurrentUser() {
+ UserInfo ui;
+ try {
+ ui = ActivityManagerNative.getDefault().getCurrentUser();
+ return ui != null ? ui.id : 0;
+ } catch (RemoteException e) {
+ return 0;
+ }
+ }
+
/**
* Returns the usage statistics of each installed package.
*
@@ -1869,4 +1969,21 @@ public class ActivityManager {
return false;
}
}
+
+ /**
+ * Return whether the given user is actively running. This means that
+ * the user is in the "started" state, not "stopped" -- it is currently
+ * allowed to run code through scheduled alarms, receiving broadcasts,
+ * etc. A started user may be either the current foreground user or a
+ * background user; the result here does not distinguish between the two.
+ * @param userid the user's id. Zero indicates the default user.
+ * @hide
+ */
+ public boolean isUserRunning(int userid) {
+ try {
+ return ActivityManagerNative.getDefault().isUserRunning(userid);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index e12fa19..83acb4d 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -39,6 +39,7 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Singleton;
@@ -88,11 +89,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
* Convenience for sending a sticky broadcast. For internal use only.
* If you don't care about permission, use null.
*/
- static public void broadcastStickyIntent(Intent intent, String permission) {
+ static public void broadcastStickyIntent(Intent intent, String permission, int userId) {
try {
getDefault().broadcastIntent(
null, intent, null, null, Activity.RESULT_OK, null, null,
- null /*permission*/, false, true, Binder.getOrigCallingUser());
+ null /*permission*/, false, true, userId);
} catch (RemoteException ex) {
}
}
@@ -135,6 +136,31 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case START_ACTIVITY_AS_USER_TRANSACTION:
+ {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder b = data.readStrongBinder();
+ IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ Intent intent = Intent.CREATOR.createFromParcel(data);
+ String resolvedType = data.readString();
+ IBinder resultTo = data.readStrongBinder();
+ String resultWho = data.readString();
+ int requestCode = data.readInt();
+ int startFlags = data.readInt();
+ String profileFile = data.readString();
+ ParcelFileDescriptor profileFd = data.readInt() != 0
+ ? data.readFileDescriptor() : null;
+ Bundle options = data.readInt() != 0
+ ? Bundle.CREATOR.createFromParcel(data) : null;
+ int userId = data.readInt();
+ int result = startActivityAsUser(app, intent, resolvedType,
+ resultTo, resultWho, requestCode, startFlags,
+ profileFile, profileFd, options, userId);
+ reply.writeNoException();
+ reply.writeInt(result);
+ return true;
+ }
+
case START_ACTIVITY_AND_WAIT_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
@@ -151,9 +177,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
? data.readFileDescriptor() : null;
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
+ int userId = data.readInt();
WaitResult result = startActivityAndWait(app, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags,
- profileFile, profileFd, options);
+ profileFile, profileFd, options, userId);
reply.writeNoException();
result.writeToParcel(reply, 0);
return true;
@@ -173,8 +200,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Configuration config = Configuration.CREATOR.createFromParcel(data);
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
+ int userId = data.readInt();
int result = startActivityWithConfig(app, intent, resolvedType,
- resultTo, resultWho, requestCode, startFlags, config, options);
+ resultTo, resultWho, requestCode, startFlags, config, options, userId);
reply.writeNoException();
reply.writeInt(result);
return true;
@@ -273,7 +301,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
= b != null ? IIntentReceiver.Stub.asInterface(b) : null;
IntentFilter filter = IntentFilter.CREATOR.createFromParcel(data);
String perm = data.readString();
- Intent intent = registerReceiver(app, packageName, rec, filter, perm);
+ int userId = data.readInt();
+ Intent intent = registerReceiver(app, packageName, rec, filter, perm, userId);
reply.writeNoException();
if (intent != null) {
reply.writeInt(1);
@@ -375,6 +404,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case ACTIVITY_RESUMED_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ activityResumed(token);
+ reply.writeNoException();
+ return true;
+ }
+
case ACTIVITY_PAUSED_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -454,13 +491,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
int maxNum = data.readInt();
int fl = data.readInt();
+ int userId = data.readInt();
List<ActivityManager.RecentTaskInfo> list = getRecentTasks(maxNum,
- fl);
+ fl, userId);
reply.writeNoException();
reply.writeTypedList(list);
return true;
}
-
+
case GET_TASK_THUMBNAILS_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int id = data.readInt();
@@ -474,7 +512,21 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
}
return true;
}
-
+
+ case GET_TASK_TOP_THUMBNAIL_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int id = data.readInt();
+ Bitmap bm = getTaskTopThumbnail(id);
+ reply.writeNoException();
+ if (bm != null) {
+ reply.writeInt(1);
+ bm.writeToParcel(reply, 0);
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
+
case GET_SERVICES_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int maxNum = data.readInt();
@@ -580,8 +632,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
String name = data.readString();
+ int userId = data.readInt();
boolean stable = data.readInt() != 0;
- ContentProviderHolder cph = getContentProvider(app, name, stable);
+ ContentProviderHolder cph = getContentProvider(app, name, userId, stable);
reply.writeNoException();
if (cph != null) {
reply.writeInt(1);
@@ -595,8 +648,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String name = data.readString();
+ int userId = data.readInt();
IBinder token = data.readStrongBinder();
- ContentProviderHolder cph = getContentProviderExternal(name, token);
+ ContentProviderHolder cph = getContentProviderExternal(name, userId, token);
reply.writeNoException();
if (cph != null) {
reply.writeInt(1);
@@ -670,7 +724,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
IApplicationThread app = ApplicationThreadNative.asInterface(b);
Intent service = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
- ComponentName cn = startService(app, service, resolvedType);
+ int userId = data.readInt();
+ ComponentName cn = startService(app, service, resolvedType, userId);
reply.writeNoException();
ComponentName.writeToParcel(cn, reply);
return true;
@@ -682,7 +737,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
IApplicationThread app = ApplicationThreadNative.asInterface(b);
Intent service = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
- int res = stopService(app, service, resolvedType);
+ int userId = data.readInt();
+ int res = stopService(app, service, resolvedType, userId);
reply.writeNoException();
reply.writeInt(res);
return true;
@@ -780,7 +836,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Bundle arguments = data.readBundle();
IBinder b = data.readStrongBinder();
IInstrumentationWatcher w = IInstrumentationWatcher.Stub.asInterface(b);
- boolean res = startInstrumentation(className, profileFile, fl, arguments, w);
+ int userId = data.readInt();
+ boolean res = startInstrumentation(className, profileFile, fl, arguments, w, userId);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -868,9 +925,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
int fl = data.readInt();
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
+ int userId = data.readInt();
IIntentSender res = getIntentSender(type, packageName, token,
resultWho, requestCode, requestIntents,
- requestResolvedTypes, fl, options);
+ requestResolvedTypes, fl, options, userId);
reply.writeNoException();
reply.writeStrongBinder(res != null ? res.asBinder() : null);
return true;
@@ -905,6 +963,22 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case HANDLE_INCOMING_USER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int callingPid = data.readInt();
+ int callingUid = data.readInt();
+ int userId = data.readInt();
+ boolean allowAll = data.readInt() != 0 ;
+ boolean requireFull = data.readInt() != 0;
+ String name = data.readString();
+ String callerPackage = data.readString();
+ int res = handleIncomingUser(callingPid, callingUid, userId, allowAll,
+ requireFull, name, callerPackage);
+ reply.writeNoException();
+ reply.writeInt(res);
+ return true;
+ }
+
case SET_PROCESS_LIMIT_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int max = data.readInt();
@@ -1164,7 +1238,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case KILL_BACKGROUND_PROCESSES_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String packageName = data.readString();
- killBackgroundProcesses(packageName);
+ int userId = data.readInt();
+ killBackgroundProcesses(packageName, userId);
reply.writeNoException();
return true;
}
@@ -1179,7 +1254,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case FORCE_STOP_PACKAGE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String packageName = data.readString();
- forceStopPackage(packageName);
+ int userId = data.readInt();
+ forceStopPackage(packageName, userId);
reply.writeNoException();
return true;
}
@@ -1205,12 +1281,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case PROFILE_CONTROL_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String process = data.readString();
+ int userId = data.readInt();
boolean start = data.readInt() != 0;
int profileType = data.readInt();
String path = data.readString();
ParcelFileDescriptor fd = data.readInt() != 0
? data.readFileDescriptor() : null;
- boolean res = profileControl(process, start, path, fd, profileType);
+ boolean res = profileControl(process, userId, start, path, fd, profileType);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -1275,30 +1352,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case START_ACTIVITY_IN_PACKAGE_TRANSACTION:
- {
- data.enforceInterface(IActivityManager.descriptor);
- int uid = data.readInt();
- Intent intent = Intent.CREATOR.createFromParcel(data);
- String resolvedType = data.readString();
- IBinder resultTo = data.readStrongBinder();
- String resultWho = data.readString();
- int requestCode = data.readInt();
- int startFlags = data.readInt();
- Bundle options = data.readInt() != 0
- ? Bundle.CREATOR.createFromParcel(data) : null;
- int result = startActivityInPackage(uid, intent, resolvedType,
- resultTo, resultWho, requestCode, startFlags, options);
- reply.writeNoException();
- reply.writeInt(result);
- return true;
- }
-
- case KILL_APPLICATION_WITH_UID_TRANSACTION: {
+ case KILL_APPLICATION_WITH_APPID_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String pkg = data.readString();
- int uid = data.readInt();
- killApplicationWithUid(pkg, uid);
+ int appid = data.readInt();
+ killApplicationWithAppId(pkg, appid);
reply.writeNoException();
return true;
}
@@ -1395,7 +1453,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case GET_PROVIDER_MIME_TYPE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
Uri uri = Uri.CREATOR.createFromParcel(data);
- String type = getProviderMimeType(uri);
+ int userId = data.readInt();
+ String type = getProviderMimeType(uri, userId);
reply.writeNoException();
reply.writeString(type);
return true;
@@ -1450,32 +1509,17 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case DUMP_HEAP_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String process = data.readString();
+ int userId = data.readInt();
boolean managed = data.readInt() != 0;
String path = data.readString();
ParcelFileDescriptor fd = data.readInt() != 0
? data.readFileDescriptor() : null;
- boolean res = dumpHeap(process, managed, path, fd);
+ boolean res = dumpHeap(process, userId, managed, path, fd);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
}
- case START_ACTIVITIES_IN_PACKAGE_TRANSACTION:
- {
- data.enforceInterface(IActivityManager.descriptor);
- int uid = data.readInt();
- Intent[] intents = data.createTypedArray(Intent.CREATOR);
- String[] resolvedTypes = data.createStringArray();
- IBinder resultTo = data.readStrongBinder();
- Bundle options = data.readInt() != 0
- ? Bundle.CREATOR.createFromParcel(data) : null;
- int result = startActivitiesInPackage(uid, intents, resolvedTypes,
- resultTo, options);
- reply.writeNoException();
- reply.writeInt(result);
- return true;
- }
-
case START_ACTIVITIES_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
@@ -1486,8 +1530,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
IBinder resultTo = data.readStrongBinder();
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
+ int userId = data.readInt();
int result = startActivities(app, intents, resolvedTypes, resultTo,
- options);
+ options, userId);
reply.writeNoException();
reply.writeInt(result);
return true;
@@ -1541,6 +1586,17 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case STOP_USER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int userid = data.readInt();
+ IStopUserCallback callback = IStopUserCallback.Stub.asInterface(
+ data.readStrongBinder());
+ int result = stopUser(userid, callback);
+ reply.writeNoException();
+ reply.writeInt(result);
+ return true;
+ }
+
case GET_CURRENT_USER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
UserInfo userInfo = getCurrentUser();
@@ -1549,6 +1605,23 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case IS_USER_RUNNING_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int userid = data.readInt();
+ boolean result = isUserRunning(userid);
+ reply.writeNoException();
+ reply.writeInt(result ? 1 : 0);
+ return true;
+ }
+
+ case GET_RUNNING_USER_IDS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int[] result = getRunningUserIds();
+ reply.writeNoException();
+ reply.writeIntArray(result);
+ return true;
+ }
+
case REMOVE_SUB_TASK_TRANSACTION:
{
data.enforceInterface(IActivityManager.descriptor);
@@ -1694,6 +1767,28 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case REGISTER_USER_SWITCH_OBSERVER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IUserSwitchObserver observer = IUserSwitchObserver.Stub.asInterface(
+ data.readStrongBinder());
+ registerUserSwitchObserver(observer);
+ return true;
+ }
+
+ case UNREGISTER_USER_SWITCH_OBSERVER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IUserSwitchObserver observer = IUserSwitchObserver.Stub.asInterface(
+ data.readStrongBinder());
+ unregisterUserSwitchObserver(observer);
+ return true;
+ }
+
+ case REQUEST_BUG_REPORT_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ requestBugReport();
+ return true;
+ }
+
}
return super.onTransact(code, data, reply, flags);
@@ -1764,10 +1859,46 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
return result;
}
+
+ public int startActivityAsUser(IApplicationThread caller, Intent intent,
+ String resolvedType, IBinder resultTo, String resultWho, int requestCode,
+ int startFlags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+ intent.writeToParcel(data, 0);
+ data.writeString(resolvedType);
+ data.writeStrongBinder(resultTo);
+ data.writeString(resultWho);
+ data.writeInt(requestCode);
+ data.writeInt(startFlags);
+ data.writeString(profileFile);
+ if (profileFd != null) {
+ data.writeInt(1);
+ profileFd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ data.writeInt(0);
+ }
+ if (options != null) {
+ data.writeInt(1);
+ options.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
+ data.writeInt(userId);
+ mRemote.transact(START_ACTIVITY_AS_USER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int result = reply.readInt();
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent,
String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, String profileFile,
- ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
+ ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -1791,6 +1922,7 @@ class ActivityManagerProxy implements IActivityManager
} else {
data.writeInt(0);
}
+ data.writeInt(userId);
mRemote.transact(START_ACTIVITY_AND_WAIT_TRANSACTION, data, reply, 0);
reply.readException();
WaitResult result = WaitResult.CREATOR.createFromParcel(reply);
@@ -1801,7 +1933,7 @@ class ActivityManagerProxy implements IActivityManager
public int startActivityWithConfig(IApplicationThread caller, Intent intent,
String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, Configuration config,
- Bundle options) throws RemoteException {
+ Bundle options, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -1819,6 +1951,7 @@ class ActivityManagerProxy implements IActivityManager
} else {
data.writeInt(0);
}
+ data.writeInt(userId);
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
@@ -1939,7 +2072,7 @@ class ActivityManagerProxy implements IActivityManager
}
public Intent registerReceiver(IApplicationThread caller, String packageName,
IIntentReceiver receiver,
- IntentFilter filter, String perm) throws RemoteException
+ IntentFilter filter, String perm, int userId) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -1949,6 +2082,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeStrongBinder(receiver != null ? receiver.asBinder() : null);
filter.writeToParcel(data, 0);
data.writeString(perm);
+ data.writeInt(userId);
mRemote.transact(REGISTER_RECEIVER_TRANSACTION, data, reply, 0);
reply.readException();
Intent intent = null;
@@ -2057,6 +2191,17 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
+ public void activityResumed(IBinder token) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ mRemote.transact(ACTIVITY_RESUMED_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
public void activityPaused(IBinder token) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -2163,12 +2308,13 @@ class ActivityManagerProxy implements IActivityManager
return list;
}
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
- int flags) throws RemoteException {
+ int flags, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeInt(maxNum);
data.writeInt(flags);
+ data.writeInt(userId);
mRemote.transact(GET_RECENT_TASKS_TRANSACTION, data, reply, 0);
reply.readException();
ArrayList<ActivityManager.RecentTaskInfo> list
@@ -2192,6 +2338,21 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return bm;
}
+ public Bitmap getTaskTopThumbnail(int id) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(id);
+ mRemote.transact(GET_TASK_TOP_THUMBNAIL_TRANSACTION, data, reply, 0);
+ reply.readException();
+ Bitmap bm = null;
+ if (reply.readInt() != 0) {
+ bm = Bitmap.CREATOR.createFromParcel(reply);
+ }
+ data.recycle();
+ reply.recycle();
+ return bm;
+ }
public List getServices(int maxNum, int flags) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2343,12 +2504,13 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
public ContentProviderHolder getContentProvider(IApplicationThread caller,
- String name, boolean stable) throws RemoteException {
+ String name, int userId, boolean stable) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
data.writeString(name);
+ data.writeInt(userId);
data.writeInt(stable ? 1 : 0);
mRemote.transact(GET_CONTENT_PROVIDER_TRANSACTION, data, reply, 0);
reply.readException();
@@ -2361,13 +2523,13 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return cph;
}
- public ContentProviderHolder getContentProviderExternal(String name, IBinder token)
- throws RemoteException
- {
+ public ContentProviderHolder getContentProviderExternal(String name, int userId, IBinder token)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(name);
+ data.writeInt(userId);
data.writeStrongBinder(token);
mRemote.transact(GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION, data, reply, 0);
reply.readException();
@@ -2459,7 +2621,7 @@ class ActivityManagerProxy implements IActivityManager
}
public ComponentName startService(IApplicationThread caller, Intent service,
- String resolvedType) throws RemoteException
+ String resolvedType, int userId) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2467,6 +2629,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
service.writeToParcel(data, 0);
data.writeString(resolvedType);
+ data.writeInt(userId);
mRemote.transact(START_SERVICE_TRANSACTION, data, reply, 0);
reply.readException();
ComponentName res = ComponentName.readFromParcel(reply);
@@ -2475,7 +2638,7 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
public int stopService(IApplicationThread caller, Intent service,
- String resolvedType) throws RemoteException
+ String resolvedType, int userId) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2483,6 +2646,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
service.writeToParcel(data, 0);
data.writeString(resolvedType);
+ data.writeInt(userId);
mRemote.transact(STOP_SERVICE_TRANSACTION, data, reply, 0);
reply.readException();
int res = reply.readInt();
@@ -2654,7 +2818,7 @@ class ActivityManagerProxy implements IActivityManager
}
public boolean startInstrumentation(ComponentName className, String profileFile,
- int flags, Bundle arguments, IInstrumentationWatcher watcher)
+ int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2664,6 +2828,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(flags);
data.writeBundle(arguments);
data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
+ data.writeInt(userId);
mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
@@ -2761,7 +2926,7 @@ class ActivityManagerProxy implements IActivityManager
public IIntentSender getIntentSender(int type,
String packageName, IBinder token, String resultWho,
int requestCode, Intent[] intents, String[] resolvedTypes, int flags,
- Bundle options) throws RemoteException {
+ Bundle options, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -2784,6 +2949,7 @@ class ActivityManagerProxy implements IActivityManager
} else {
data.writeInt(0);
}
+ data.writeInt(userId);
mRemote.transact(GET_INTENT_SENDER_TRANSACTION, data, reply, 0);
reply.readException();
IIntentSender res = IIntentSender.Stub.asInterface(
@@ -2826,6 +2992,25 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return res;
}
+ public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
+ boolean requireFull, String name, String callerPackage) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(callingPid);
+ data.writeInt(callingUid);
+ data.writeInt(userId);
+ data.writeInt(allowAll ? 1 : 0);
+ data.writeInt(requireFull ? 1 : 0);
+ data.writeString(name);
+ data.writeString(callerPackage);
+ mRemote.transact(HANDLE_INCOMING_USER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int res = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
public void setProcessLimit(int max) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -3165,11 +3350,12 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
- public void killBackgroundProcesses(String packageName) throws RemoteException {
+ public void killBackgroundProcesses(String packageName, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(packageName);
+ data.writeInt(userId);
mRemote.transact(KILL_BACKGROUND_PROCESSES_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
@@ -3186,11 +3372,12 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
- public void forceStopPackage(String packageName) throws RemoteException {
+ public void forceStopPackage(String packageName, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(packageName);
+ data.writeInt(userId);
mRemote.transact(FORCE_STOP_PACKAGE_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
@@ -3223,13 +3410,14 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
- public boolean profileControl(String process, boolean start,
+ public boolean profileControl(String process, int userId, boolean start,
String path, ParcelFileDescriptor fd, int profileType) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(process);
+ data.writeInt(userId);
data.writeInt(start ? 1 : 0);
data.writeInt(profileType);
data.writeString(path);
@@ -3281,41 +3469,13 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
}
- public int startActivityInPackage(int uid,
- Intent intent, String resolvedType, IBinder resultTo,
- String resultWho, int requestCode, int startFlags, Bundle options)
- throws RemoteException {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- data.writeInterfaceToken(IActivityManager.descriptor);
- data.writeInt(uid);
- intent.writeToParcel(data, 0);
- data.writeString(resolvedType);
- data.writeStrongBinder(resultTo);
- data.writeString(resultWho);
- data.writeInt(requestCode);
- data.writeInt(startFlags);
- if (options != null) {
- data.writeInt(1);
- options.writeToParcel(data, 0);
- } else {
- data.writeInt(0);
- }
- mRemote.transact(START_ACTIVITY_IN_PACKAGE_TRANSACTION, data, reply, 0);
- reply.readException();
- int result = reply.readInt();
- reply.recycle();
- data.recycle();
- return result;
- }
-
- public void killApplicationWithUid(String pkg, int uid) throws RemoteException {
+ public void killApplicationWithAppId(String pkg, int appid) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(pkg);
- data.writeInt(uid);
- mRemote.transact(KILL_APPLICATION_WITH_UID_TRANSACTION, data, reply, 0);
+ data.writeInt(appid);
+ mRemote.transact(KILL_APPLICATION_WITH_APPID_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
@@ -3450,12 +3610,12 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
- public String getProviderMimeType(Uri uri)
- throws RemoteException {
+ public String getProviderMimeType(Uri uri, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
uri.writeToParcel(data, 0);
+ data.writeInt(userId);
mRemote.transact(GET_PROVIDER_MIME_TYPE_TRANSACTION, data, reply, 0);
reply.readException();
String res = reply.readString();
@@ -3530,12 +3690,13 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
- public boolean dumpHeap(String process, boolean managed,
+ public boolean dumpHeap(String process, int userId, boolean managed,
String path, ParcelFileDescriptor fd) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(process);
+ data.writeInt(userId);
data.writeInt(managed ? 1 : 0);
data.writeString(path);
if (fd != null) {
@@ -3554,7 +3715,7 @@ class ActivityManagerProxy implements IActivityManager
public int startActivities(IApplicationThread caller,
Intent[] intents, String[] resolvedTypes, IBinder resultTo,
- Bundle options) throws RemoteException {
+ Bundle options, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3568,6 +3729,7 @@ class ActivityManagerProxy implements IActivityManager
} else {
data.writeInt(0);
}
+ data.writeInt(userId);
mRemote.transact(START_ACTIVITIES_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
@@ -3576,30 +3738,6 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
- public int startActivitiesInPackage(int uid,
- Intent[] intents, String[] resolvedTypes, IBinder resultTo,
- Bundle options) throws RemoteException {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- data.writeInterfaceToken(IActivityManager.descriptor);
- data.writeInt(uid);
- data.writeTypedArray(intents, 0);
- data.writeStringArray(resolvedTypes);
- data.writeStrongBinder(resultTo);
- if (options != null) {
- data.writeInt(1);
- options.writeToParcel(data, 0);
- } else {
- data.writeInt(0);
- }
- mRemote.transact(START_ACTIVITIES_IN_PACKAGE_TRANSACTION, data, reply, 0);
- reply.readException();
- int result = reply.readInt();
- reply.recycle();
- data.recycle();
- return result;
- }
-
public int getFrontActivityScreenCompatMode() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -3688,11 +3826,25 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
+ public int stopUser(int userid, IStopUserCallback callback) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(userid);
+ data.writeStrongInterface(callback);
+ mRemote.transact(STOP_USER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int result = reply.readInt();
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+
public UserInfo getCurrentUser() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
- mRemote.transact(SWITCH_USER_TRANSACTION, data, reply, 0);
+ mRemote.transact(GET_CURRENT_USER_TRANSACTION, data, reply, 0);
reply.readException();
UserInfo userInfo = UserInfo.CREATOR.createFromParcel(reply);
reply.recycle();
@@ -3700,6 +3852,31 @@ class ActivityManagerProxy implements IActivityManager
return userInfo;
}
+ public boolean isUserRunning(int userid) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(userid);
+ mRemote.transact(IS_USER_RUNNING_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean result = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+
+ public int[] getRunningUserIds() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_RUNNING_USER_IDS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int[] result = reply.createIntArray();
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+
public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -3873,5 +4050,37 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
+ public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+ mRemote.transact(REGISTER_USER_SWITCH_OBSERVER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ public void unregisterUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(observer != null ? observer.asBinder() : null);
+ mRemote.transact(UNREGISTER_USER_SWITCH_OBSERVER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ public void requestBugReport() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(REQUEST_BUG_REPORT_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 4edfdfb..87b1e24 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -97,9 +97,9 @@ public class ActivityOptions {
/** @hide */
public static final int ANIM_SCALE_UP = 2;
/** @hide */
- public static final int ANIM_THUMBNAIL = 3;
+ public static final int ANIM_THUMBNAIL_SCALE_UP = 3;
/** @hide */
- public static final int ANIM_THUMBNAIL_DELAYED = 4;
+ public static final int ANIM_THUMBNAIL_SCALE_DOWN = 4;
private String mPackageName;
private int mAnimationType = ANIM_NONE;
@@ -262,20 +262,19 @@ public class ActivityOptions {
*/
public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
- return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, listener, false);
+ return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, true);
}
/**
- * Create an ActivityOptions specifying an animation where a thumbnail
- * is scaled from a given position to the new activity window that is
- * being started. Before the animation, there is a short delay.
+ * Create an ActivityOptions specifying an animation where an activity window
+ * is scaled from a given position to a thumbnail at a specified location.
*
- * @param source The View that this thumbnail is animating from. This
+ * @param source The View that this thumbnail is animating to. This
* defines the coordinate space for <var>startX</var> and <var>startY</var>.
- * @param thumbnail The bitmap that will be shown as the initial thumbnail
+ * @param thumbnail The bitmap that will be shown as the final thumbnail
* of the animation.
- * @param startX The x starting location of the bitmap, relative to <var>source</var>.
- * @param startY The y starting location of the bitmap, relative to <var>source</var>.
+ * @param startX The x end location of the bitmap, relative to <var>source</var>.
+ * @param startY The y end location of the bitmap, relative to <var>source</var>.
* @param listener Optional OnAnimationStartedListener to find out when the
* requested animation has started running. If for some reason the animation
* is not executed, the callback will happen immediately.
@@ -283,17 +282,17 @@ public class ActivityOptions {
* supply these options as the options Bundle when starting an activity.
* @hide
*/
- public static ActivityOptions makeDelayedThumbnailScaleUpAnimation(View source,
+ public static ActivityOptions makeThumbnailScaleDownAnimation(View source,
Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
- return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, listener, true);
+ return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, false);
}
- private static ActivityOptions makeThumbnailScaleUpAnimation(View source,
+ private static ActivityOptions makeThumbnailAnimation(View source,
Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener,
- boolean delayed) {
+ boolean scaleUp) {
ActivityOptions opts = new ActivityOptions();
opts.mPackageName = source.getContext().getPackageName();
- opts.mAnimationType = delayed ? ANIM_THUMBNAIL_DELAYED : ANIM_THUMBNAIL;
+ opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN;
opts.mThumbnail = thumbnail;
int[] pts = new int[2];
source.getLocationOnScreen(pts);
@@ -320,8 +319,8 @@ public class ActivityOptions {
mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
mStartWidth = opts.getInt(KEY_ANIM_START_WIDTH, 0);
mStartHeight = opts.getInt(KEY_ANIM_START_HEIGHT, 0);
- } else if (mAnimationType == ANIM_THUMBNAIL ||
- mAnimationType == ANIM_THUMBNAIL_DELAYED) {
+ } else if (mAnimationType == ANIM_THUMBNAIL_SCALE_UP ||
+ mAnimationType == ANIM_THUMBNAIL_SCALE_DOWN) {
mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL);
mStartX = opts.getInt(KEY_ANIM_START_X, 0);
mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
@@ -434,8 +433,8 @@ public class ActivityOptions {
}
mAnimationStartedListener = null;
break;
- case ANIM_THUMBNAIL:
- case ANIM_THUMBNAIL_DELAYED:
+ case ANIM_THUMBNAIL_SCALE_UP:
+ case ANIM_THUMBNAIL_SCALE_DOWN:
mAnimationType = otherOptions.mAnimationType;
mThumbnail = otherOptions.mThumbnail;
mStartX = otherOptions.mStartX;
@@ -479,8 +478,8 @@ public class ActivityOptions {
b.putInt(KEY_ANIM_START_WIDTH, mStartWidth);
b.putInt(KEY_ANIM_START_HEIGHT, mStartHeight);
break;
- case ANIM_THUMBNAIL:
- case ANIM_THUMBNAIL_DELAYED:
+ case ANIM_THUMBNAIL_SCALE_UP:
+ case ANIM_THUMBNAIL_SCALE_DOWN:
b.putInt(KEY_ANIM_TYPE, mAnimationType);
b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail);
b.putInt(KEY_ANIM_START_X, mStartX);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b7e0683..ef9f6d4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -42,6 +42,8 @@ import android.database.sqlite.SQLiteDebug;
import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.net.IConnectivityManager;
import android.net.Proxy;
import android.net.ProxyProperties;
@@ -50,6 +52,8 @@ import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
+import android.os.DropBoxManager;
+import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -61,8 +65,9 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.Trace;
-import android.os.UserId;
+import android.os.UserHandle;
import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.EventLog;
@@ -70,6 +75,7 @@ import android.util.Log;
import android.util.LogPrinter;
import android.util.PrintWriterPrinter;
import android.util.Slog;
+import android.view.CompatibilityInfoHolder;
import android.view.Display;
import android.view.HardwareRenderer;
import android.view.View;
@@ -78,12 +84,13 @@ import android.view.ViewManager;
import android.view.ViewRootImpl;
import android.view.Window;
import android.view.WindowManager;
-import android.view.WindowManagerImpl;
+import android.view.WindowManagerGlobal;
import android.renderscript.RenderScript;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SamplingProfilerIntegration;
+import com.android.internal.util.Objects;
import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
@@ -94,6 +101,7 @@ import java.io.IOException;
import java.io.PrintWriter;
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;
@@ -103,6 +111,8 @@ import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Pattern;
+import libcore.io.DropBox;
+import libcore.io.EventLogger;
import libcore.io.IoUtils;
import dalvik.system.CloseGuard;
@@ -165,6 +175,8 @@ public final class ActivityThread {
= new HashMap<IBinder, Service>();
AppBindData mBoundApplication;
Profiler mProfiler;
+ int mCurDefaultDisplayDpi;
+ boolean mDensityCompatMode;
Configuration mConfiguration;
Configuration mCompatConfiguration;
Configuration mResConfiguration;
@@ -196,7 +208,7 @@ public final class ActivityThread {
= new HashMap<String, WeakReference<LoadedApk>>();
final HashMap<String, WeakReference<LoadedApk>> mResourcePackages
= new HashMap<String, WeakReference<LoadedApk>>();
- final HashMap<CompatibilityInfo, DisplayMetrics> mDisplayMetrics
+ final HashMap<CompatibilityInfo, DisplayMetrics> mDefaultDisplayMetrics
= new HashMap<CompatibilityInfo, DisplayMetrics>();
final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources
= new HashMap<ResourcesKey, WeakReference<Resources> >();
@@ -204,9 +216,33 @@ public final class ActivityThread {
= new ArrayList<ActivityClientRecord>();
Configuration mPendingConfiguration = null;
+ private static final class ProviderKey {
+ final String authority;
+ final int userId;
+
+ public ProviderKey(String authority, int userId) {
+ this.authority = authority;
+ this.userId = userId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ProviderKey) {
+ final ProviderKey other = (ProviderKey) o;
+ return Objects.equal(authority, other.authority) && userId == other.userId;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return ((authority != null) ? authority.hashCode() : 0) ^ userId;
+ }
+ }
+
// The lock of mProviderMap protects the following variables.
- final HashMap<String, ProviderClientRecord> mProviderMap
- = new HashMap<String, ProviderClientRecord>();
+ final HashMap<ProviderKey, ProviderClientRecord> mProviderMap
+ = new HashMap<ProviderKey, ProviderClientRecord>();
final HashMap<IBinder, ProviderRefCount> mProviderRefCountMap
= new HashMap<IBinder, ProviderRefCount>();
final HashMap<IBinder, ProviderClientRecord> mLocalProviders
@@ -313,8 +349,9 @@ public final class ActivityThread {
static final class ReceiverData extends BroadcastReceiver.PendingResult {
public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
- boolean ordered, boolean sticky, IBinder token) {
- super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky, token);
+ boolean ordered, boolean sticky, IBinder token, int sendingUser) {
+ super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky,
+ token, sendingUser);
this.intent = intent;
}
@@ -606,9 +643,9 @@ public final class ActivityThread {
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
- boolean sync) {
+ boolean sync, int sendingUser) {
ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
- sync, false, mAppThread.asBinder());
+ sync, false, mAppThread.asBinder(), sendingUser);
r.info = info;
r.compatInfo = compatInfo;
queueOrSendMessage(H.RECEIVER, r);
@@ -767,8 +804,9 @@ 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) throws RemoteException {
- receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky);
+ boolean sticky, int sendingUser) throws RemoteException {
+ receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
+ sticky, sendingUser);
}
public void scheduleLowMemory() {
@@ -1052,7 +1090,7 @@ public final class ActivityThread {
@Override
public void dumpGfxInfo(FileDescriptor fd, String[] args) {
dumpGraphicsInfo(fd);
- WindowManagerImpl.getDefault().dumpGfxInfo(fd);
+ WindowManagerGlobal.getInstance().dumpGfxInfo(fd);
}
@Override
@@ -1235,7 +1273,7 @@ public final class ActivityThread {
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
handleResumeActivity((IBinder)msg.obj, true,
- msg.arg1 != 0);
+ msg.arg1 != 0, true);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SEND_RESULT:
@@ -1305,6 +1343,7 @@ public final class ActivityThread {
break;
case CONFIGURATION_CHANGED:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
+ mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
handleConfigurationChanged((Configuration)msg.obj, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
@@ -1467,13 +1506,28 @@ public final class ActivityThread {
private static class ResourcesKey {
final private String mResDir;
+ final private int mDisplayId;
+ final private Configuration mOverrideConfiguration;
final private float mScale;
final private int mHash;
- ResourcesKey(String resDir, float scale) {
+ ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) {
mResDir = resDir;
+ mDisplayId = displayId;
+ if (overrideConfiguration != null) {
+ if (Configuration.EMPTY.equals(overrideConfiguration)) {
+ overrideConfiguration = null;
+ }
+ }
+ mOverrideConfiguration = overrideConfiguration;
mScale = scale;
- mHash = mResDir.hashCode() << 2 + (int) (mScale * 2);
+ int hash = 17;
+ hash = 31 * hash + mResDir.hashCode();
+ hash = 31 * hash + mDisplayId;
+ hash = 31 * hash + (mOverrideConfiguration != null
+ ? mOverrideConfiguration.hashCode() : 0);
+ hash = 31 * hash + Float.floatToIntBits(mScale);
+ mHash = hash;
}
@Override
@@ -1487,7 +1541,24 @@ public final class ActivityThread {
return false;
}
ResourcesKey peer = (ResourcesKey) obj;
- return mResDir.equals(peer.mResDir) && mScale == peer.mScale;
+ 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;
}
}
@@ -1518,17 +1589,41 @@ public final class ActivityThread {
return sPackageManager;
}
- DisplayMetrics getDisplayMetricsLocked(CompatibilityInfo ci, boolean forceUpdate) {
- DisplayMetrics dm = mDisplayMetrics.get(ci);
- if (dm != null && !forceUpdate) {
+ private void flushDisplayMetricsLocked() {
+ mDefaultDisplayMetrics.clear();
+ }
+
+ DisplayMetrics getDisplayMetricsLocked(int displayId, CompatibilityInfo ci) {
+ boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+ DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(ci) : 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 (dm == null) {
- dm = new DisplayMetrics();
- mDisplayMetrics.put(ci, dm);
+
+ if (isDefaultDisplay) {
+ mDefaultDisplayMetrics.put(ci, dm);
+ }
+
+ CompatibilityInfoHolder cih = new CompatibilityInfoHolder();
+ cih.set(ci);
+ Display d = displayManager.getCompatibleDisplay(displayId, cih);
+ 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();
}
- Display d = WindowManagerImpl.getDefault(ci).getDefaultDisplay();
- d.getMetrics(dm);
//Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h="
// + metrics.heightPixels + " den=" + metrics.density
// + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi);
@@ -1536,14 +1631,15 @@ public final class ActivityThread {
}
private Configuration mMainThreadConfig = new Configuration();
- Configuration applyConfigCompatMainThread(Configuration config, CompatibilityInfo compat) {
+ Configuration applyConfigCompatMainThread(int displayDensity, Configuration config,
+ CompatibilityInfo compat) {
if (config == null) {
return null;
}
if (compat != null && !compat.supportsScreen()) {
mMainThreadConfig.setTo(config);
config = mMainThreadConfig;
- compat.applyToConfiguration(config);
+ compat.applyToConfiguration(displayDensity, config);
}
return config;
}
@@ -1555,8 +1651,12 @@ public final class ActivityThread {
* @param compInfo the compability info. It will use the default compatibility info when it's
* null.
*/
- Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {
- ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);
+ Resources getTopLevelResources(String resDir,
+ int displayId, Configuration overrideConfiguration,
+ CompatibilityInfo compInfo) {
+ ResourcesKey key = new ResourcesKey(resDir,
+ displayId, overrideConfiguration,
+ compInfo.applicationScale);
Resources r;
synchronized (mPackages) {
// Resources is app scale dependent.
@@ -1587,14 +1687,27 @@ public final class ActivityThread {
}
//Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
- DisplayMetrics metrics = getDisplayMetricsLocked(null, false);
- r = new Resources(assets, metrics, getConfiguration(), compInfo);
+ 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<Resources> wr = mActiveResources.get(key);
Resources existing = wr != null ? wr.get() : null;
@@ -1614,8 +1727,11 @@ public final class ActivityThread {
/**
* Creates the top level resources for the given package.
*/
- Resources getTopLevelResources(String resDir, LoadedApk pkgInfo) {
- return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo.get());
+ Resources getTopLevelResources(String resDir,
+ int displayId, Configuration overrideConfiguration,
+ LoadedApk pkgInfo) {
+ return getTopLevelResources(resDir, displayId, overrideConfiguration,
+ pkgInfo.mCompatibilityInfo.get());
}
final Handler getHandler() {
@@ -1624,6 +1740,11 @@ public final class ActivityThread {
public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
int flags) {
+ return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId());
+ }
+
+ public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
+ int flags, int userId) {
synchronized (mPackages) {
WeakReference<LoadedApk> ref;
if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) {
@@ -1652,7 +1773,7 @@ public final class ActivityThread {
ApplicationInfo ai = null;
try {
ai = getPackageManager().getApplicationInfo(packageName,
- PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId());
+ PackageManager.GET_SHARED_LIBRARY_FILES, userId);
} catch (RemoteException e) {
// Ignore
}
@@ -1669,7 +1790,7 @@ public final class ActivityThread {
boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
boolean securityViolation = includeCode && ai.uid != 0
&& ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
- ? !UserId.isSameApp(ai.uid, mBoundApplication.appInfo.uid)
+ ? !UserHandle.isSameApp(ai.uid, mBoundApplication.appInfo.uid)
: true);
if ((flags&(Context.CONTEXT_INCLUDE_CODE
|Context.CONTEXT_IGNORE_SECURITY))
@@ -1786,7 +1907,8 @@ public final class ActivityThread {
context.init(info, null, this);
context.getResources().updateConfiguration(
getConfiguration(), getDisplayMetricsLocked(
- CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, false));
+ Display.DEFAULT_DISPLAY,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO));
mSystemContext = context;
//Slog.i(TAG, "Created system resources " + context.getResources()
// + ": " + context.getResources().getConfiguration());
@@ -1998,9 +2120,7 @@ public final class ActivityThread {
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
- ContextImpl appContext = new ContextImpl();
- appContext.init(r.packageInfo, r.token, this);
- appContext.setOuterContext(activity);
+ Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
@@ -2065,6 +2185,31 @@ public final class ActivityThread {
return activity;
}
+ private Context createBaseContextForActivity(ActivityClientRecord r,
+ final Activity activity) {
+ ContextImpl appContext = new ContextImpl();
+ appContext.init(r.packageInfo, r.token, this);
+ appContext.setOuterContext(activity);
+
+ // For debugging purposes, if the activity's package name contains the value of
+ // the "debug.use-second-display" system property as a substring, then show
+ // its content on a secondary display if there is one.
+ Context baseContext = appContext;
+ String pkgName = SystemProperties.get("debug.second-display.pkg");
+ if (pkgName != null && !pkgName.isEmpty()
+ && r.packageInfo.mPackageName.contains(pkgName)) {
+ DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
+ for (int displayId : dm.getDisplayIds()) {
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ Display display = dm.getRealDisplay(displayId);
+ baseContext = appContext.createDisplayContext(display);
+ break;
+ }
+ }
+ }
+ return baseContext;
+ }
+
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
@@ -2086,7 +2231,8 @@ public final class ActivityThread {
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
- handleResumeActivity(r.token, false, r.isForward);
+ handleResumeActivity(r.token, false, r.isForward,
+ !r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
// The activity manager actually wants this one to start out
@@ -2554,6 +2700,7 @@ public final class ActivityThread {
r.activity.mStartedActivity = false;
}
try {
+ r.activity.mFragments.noteStateNotSaved();
if (r.pendingIntents != null) {
deliverNewIntents(r, r.pendingIntents);
r.pendingIntents = null;
@@ -2565,7 +2712,7 @@ public final class ActivityThread {
r.activity.performResume();
EventLog.writeEvent(LOG_ON_RESUME_CALLED,
- r.activity.getComponentName().getClassName());
+ UserHandle.myUserId(), r.activity.getComponentName().getClassName());
r.paused = false;
r.stopped = false;
@@ -2587,7 +2734,7 @@ public final class ActivityThread {
r.mPendingRemoveWindowManager.removeViewImmediate(r.mPendingRemoveWindow);
IBinder wtoken = r.mPendingRemoveWindow.getWindowToken();
if (wtoken != null) {
- WindowManagerImpl.getDefault().closeAll(wtoken,
+ WindowManagerGlobal.getInstance().closeAll(wtoken,
r.activity.getClass().getName(), "Activity");
}
}
@@ -2595,7 +2742,8 @@ public final class ActivityThread {
r.mPendingRemoveWindowManager = null;
}
- final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
+ final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
+ boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
@@ -2692,6 +2840,14 @@ public final class ActivityThread {
}
r.onlyLocalRequest = false;
+ // Tell the activity manager we have resumed.
+ if (reallyResume) {
+ try {
+ ActivityManagerNative.getDefault().activityResumed(token);
+ } catch (RemoteException ex) {
+ }
+ }
+
} else {
// If an exception was thrown when trying to resume, then
// just end this activity.
@@ -2727,7 +2883,8 @@ public final class ActivityThread {
// On platforms where we don't want thumbnails, set dims to (0,0)
if ((w > 0) && (h > 0)) {
- thumbnail = Bitmap.createBitmap(w, h, THUMBNAIL_FORMAT);
+ thumbnail = Bitmap.createBitmap(r.activity.getResources().getDisplayMetrics(),
+ w, h, THUMBNAIL_FORMAT);
thumbnail.eraseColor(0);
}
}
@@ -2775,7 +2932,7 @@ public final class ActivityThread {
if (r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
-
+
// Tell the activity manager we have paused.
try {
ActivityManagerNative.getDefault().activityPaused(token);
@@ -2823,7 +2980,8 @@ public final class ActivityThread {
// Now we are idle.
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
- EventLog.writeEvent(LOG_ON_PAUSE_CALLED, r.activity.getComponentName().getClassName());
+ EventLog.writeEvent(LOG_ON_PAUSE_CALLED, UserHandle.myUserId(),
+ r.activity.getComponentName().getClassName());
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
@@ -3121,7 +3279,7 @@ public final class ActivityThread {
apk.mCompatibilityInfo.set(data.info);
}
handleConfigurationChanged(mConfiguration, data.info);
- WindowManagerImpl.getDefault().reportNewConfiguration(mConfiguration);
+ WindowManagerGlobal.getInstance().reportNewConfiguration(mConfiguration);
}
private void deliverResults(ActivityClientRecord r, List<ResultInfo> results) {
@@ -3208,7 +3366,7 @@ public final class ActivityThread {
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
- EventLog.writeEvent(LOG_ON_PAUSE_CALLED,
+ EventLog.writeEvent(LOG_ON_PAUSE_CALLED, UserHandle.myUserId(),
r.activity.getComponentName().getClassName());
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
@@ -3310,7 +3468,7 @@ public final class ActivityThread {
}
}
if (wtoken != null && r.mPendingRemoveWindow == null) {
- WindowManagerImpl.getDefault().closeAll(wtoken,
+ WindowManagerGlobal.getInstance().closeAll(wtoken,
r.activity.getClass().getName(), "Activity");
}
r.activity.mDecor = null;
@@ -3322,7 +3480,7 @@ public final class ActivityThread {
// by the app will leak. Well we try to warning them a lot
// about leaking windows, because that is a bug, so if they are
// using this recreate facility then they get to live with leaks.
- WindowManagerImpl.getDefault().closeAll(token,
+ WindowManagerGlobal.getInstance().closeAll(token,
r.activity.getClass().getName(), "Activity");
}
@@ -3461,6 +3619,8 @@ public final class ActivityThread {
// If there was a pending configuration change, execute it first.
if (changedConfig != null) {
+ mCurDefaultDisplayDpi = changedConfig.densityDpi;
+ updateDefaultDensity();
handleConfigurationChanged(changedConfig, null);
}
@@ -3534,39 +3694,45 @@ public final class ActivityThread {
}
}
- ArrayList<ComponentCallbacks2> collectComponentCallbacksLocked(
+ ArrayList<ComponentCallbacks2> collectComponentCallbacks(
boolean allActivities, Configuration newConfig) {
ArrayList<ComponentCallbacks2> callbacks
= new ArrayList<ComponentCallbacks2>();
- if (mActivities.size() > 0) {
- for (ActivityClientRecord ar : mActivities.values()) {
- Activity a = ar.activity;
- if (a != null) {
- Configuration thisConfig = applyConfigCompatMainThread(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);
+ synchronized (mPackages) {
+ final int N = mAllApplications.size();
+ for (int i=0; i<N; i++) {
+ callbacks.add(mAllApplications.get(i));
+ }
+ if (mActivities.size() > 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;
}
- ar.newConfig = thisConfig;
}
}
}
- }
- if (mServices.size() > 0) {
- for (Service service : mServices.values()) {
- callbacks.add(service);
+ if (mServices.size() > 0) {
+ for (Service service : mServices.values()) {
+ callbacks.add(service);
+ }
}
}
synchronized (mProviderMap) {
@@ -3576,10 +3742,6 @@ public final class ActivityThread {
}
}
}
- final int N = mAllApplications.size();
- for (int i=0; i<N; i++) {
- callbacks.add(mAllApplications.get(i));
- }
return callbacks;
}
@@ -3646,7 +3808,9 @@ public final class ActivityThread {
return false;
}
int changes = mResConfiguration.updateFrom(config);
- DisplayMetrics dm = getDisplayMetricsLocked(null, true);
+ flushDisplayMetricsLocked();
+ DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(
+ Display.DEFAULT_DISPLAY, null);
if (compat != null && (mResCompatibilityInfo == null ||
!mResCompatibilityInfo.equals(compat))) {
@@ -3661,22 +3825,41 @@ public final class ActivityThread {
Locale.setDefault(config.locale);
}
- Resources.updateSystemConfiguration(config, dm, compat);
+ Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
ApplicationPackageManager.configurationChanged();
//Slog.i(TAG, "Configuration changed in " + currentPackageName());
-
- Iterator<WeakReference<Resources>> it =
- mActiveResources.values().iterator();
- //Iterator<Map.Entry<String, WeakReference<Resources>>> it =
- // mActiveResources.entrySet().iterator();
+
+ Configuration tmpConfig = null;
+
+ Iterator<Map.Entry<ResourcesKey, WeakReference<Resources>>> it =
+ mActiveResources.entrySet().iterator();
while (it.hasNext()) {
- WeakReference<Resources> v = it.next();
- Resources r = v.get();
+ Map.Entry<ResourcesKey, WeakReference<Resources>> entry = it.next();
+ Resources r = entry.getValue().get();
if (r != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ r + " config to: " + config);
- r.updateConfiguration(config, dm, compat);
+ 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 {
@@ -3688,14 +3871,36 @@ public final class ActivityThread {
return changes != 0;
}
- final Configuration applyCompatConfiguration() {
+ 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) {
Configuration config = mConfiguration;
if (mCompatConfiguration == null) {
mCompatConfiguration = new Configuration();
}
mCompatConfiguration.setTo(mConfiguration);
if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
- mResCompatibilityInfo.applyToConfiguration(mCompatConfiguration);
+ mResCompatibilityInfo.applyToConfiguration(displayDensity, mCompatConfiguration);
config = mCompatConfiguration;
}
return config;
@@ -3703,13 +3908,14 @@ public final class ActivityThread {
final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
- ArrayList<ComponentCallbacks2> callbacks = null;
int configDiff = 0;
synchronized (mPackages) {
if (mPendingConfiguration != null) {
if (!mPendingConfiguration.isOtherSeqNewer(config)) {
config = mPendingConfiguration;
+ mCurDefaultDisplayDpi = config.densityDpi;
+ updateDefaultDensity();
}
mPendingConfiguration = null;
}
@@ -3731,12 +3937,13 @@ public final class ActivityThread {
}
configDiff = mConfiguration.diff(config);
mConfiguration.updateFrom(config);
- config = applyCompatConfiguration();
- callbacks = collectComponentCallbacksLocked(false, config);
+ config = applyCompatConfiguration(mCurDefaultDisplayDpi);
}
-
+
+ ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);
+
// Cleanup hardware accelerated stuff
- WindowManagerImpl.getDefault().trimLocalMemory();
+ WindowManagerGlobal.getInstance().trimLocalMemory();
freeTextLayoutCachesIfNeeded(configDiff);
@@ -3847,11 +4054,7 @@ public final class ActivityThread {
}
final void handleLowMemory() {
- ArrayList<ComponentCallbacks2> callbacks;
-
- synchronized (mPackages) {
- callbacks = collectComponentCallbacksLocked(true, null);
- }
+ ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);
final int N = callbacks.size();
for (int i=0; i<N; i++) {
@@ -3876,20 +4079,17 @@ public final class ActivityThread {
final void handleTrimMemory(int level) {
if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
- final WindowManagerImpl windowManager = WindowManagerImpl.getDefault();
+ final WindowManagerGlobal windowManager = WindowManagerGlobal.getInstance();
windowManager.startTrimMemory(level);
- ArrayList<ComponentCallbacks2> callbacks;
- synchronized (mPackages) {
- callbacks = collectComponentCallbacksLocked(true, null);
- }
+ ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);
final int N = callbacks.size();
for (int i = 0; i < N; i++) {
callbacks.get(i).onTrimMemory(level);
}
- windowManager.endTrimMemory();
+ windowManager.endTrimMemory();
}
private void setupGraphicsSupport(LoadedApk info, File cacheDir) {
@@ -3910,8 +4110,20 @@ public final class ActivityThread {
} catch (RemoteException e) {
// Ignore
}
- }
-
+ }
+
+ private void updateDefaultDensity() {
+ if (mCurDefaultDisplayDpi != Configuration.DENSITY_DPI_UNDEFINED
+ && mCurDefaultDisplayDpi != DisplayMetrics.DENSITY_DEVICE
+ && !mDensityCompatMode) {
+ Slog.i(TAG, "Switching default density from "
+ + DisplayMetrics.DENSITY_DEVICE + " to "
+ + mCurDefaultDisplayDpi);
+ DisplayMetrics.DENSITY_DEVICE = mCurDefaultDisplayDpi;
+ Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
+ }
+ }
+
private void handleBindApplication(AppBindData data) {
mBoundApplication = data;
mConfiguration = new Configuration(data.config);
@@ -3924,14 +4136,14 @@ public final class ActivityThread {
// send up app name; do this *before* waiting for debugger
Process.setArgV0(data.processName);
- android.ddm.DdmHandleAppName.setAppName(data.processName);
+ android.ddm.DdmHandleAppName.setAppName(data.processName,
+ UserHandle.myUserId());
if (data.persistent) {
// Persistent processes on low-memory devices do not get to
// use hardware accelerated drawing, since this can add too much
// overhead to the process.
- Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
- if (!ActivityManager.isHighEndGfx(display)) {
+ if (!ActivityManager.isHighEndGfx()) {
HardwareRenderer.disable(false);
}
}
@@ -3967,19 +4179,35 @@ public final class ActivityThread {
* in AppBindData can be safely assumed to be up to date
*/
applyConfigurationToResourcesLocked(data.config, data.compatInfo);
- applyCompatConfiguration();
+ mCurDefaultDisplayDpi = data.config.densityDpi;
+ applyCompatConfiguration(mCurDefaultDisplayDpi);
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
+ /**
+ * Switch this process to density compatibility mode if needed.
+ */
+ if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
+ == 0) {
+ mDensityCompatMode = true;
+ Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
+ }
+ updateDefaultDensity();
+
final ContextImpl appContext = new ContextImpl();
appContext.init(data.info, null, this);
- final File cacheDir = appContext.getCacheDir();
-
- // Provide a usable directory for temporary files
- System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
-
- setupGraphicsSupport(data.info, cacheDir);
+ if (!Process.isIsolated()) {
+ final File cacheDir = appContext.getCacheDir();
+ if (cacheDir != null) {
+ // Provide a usable directory for temporary files
+ System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
+
+ setupGraphicsSupport(data.info, cacheDir);
+ } else {
+ Log.e(TAG, "Unable to setupGraphicsSupport due to missing cache directory");
+ }
+ }
/**
* For system applications on userdebug/eng builds, log stack
* traces of disk and network access to dropbox for analysis.
@@ -4001,14 +4229,6 @@ public final class ActivityThread {
StrictMode.enableDeathOnNetwork();
}
- /**
- * Switch this process to density compatibility mode if needed.
- */
- if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
- == 0) {
- Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
- }
-
if (data.debugMode != IApplicationThread.DEBUG_OFF) {
// XXX should have option to change the port.
Debug.changeDebugPort(8100);
@@ -4202,8 +4422,9 @@ public final class ActivityThread {
}
}
- public final IContentProvider acquireProvider(Context c, String name, boolean stable) {
- IContentProvider provider = acquireExistingProvider(c, name, stable);
+ public final IContentProvider acquireProvider(
+ Context c, String auth, int userId, boolean stable) {
+ final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
if (provider != null) {
return provider;
}
@@ -4217,11 +4438,11 @@ public final class ActivityThread {
IActivityManager.ContentProviderHolder holder = null;
try {
holder = ActivityManagerNative.getDefault().getContentProvider(
- getApplicationThread(), name, stable);
+ getApplicationThread(), auth, userId, stable);
} catch (RemoteException ex) {
}
if (holder == null) {
- Slog.e(TAG, "Failed to find provider info for " + name);
+ Slog.e(TAG, "Failed to find provider info for " + auth);
return null;
}
@@ -4298,10 +4519,11 @@ public final class ActivityThread {
}
}
- public final IContentProvider acquireExistingProvider(Context c, String name,
- boolean stable) {
+ public final IContentProvider acquireExistingProvider(
+ Context c, String auth, int userId, boolean stable) {
synchronized (mProviderMap) {
- ProviderClientRecord pr = mProviderMap.get(name);
+ final ProviderKey key = new ProviderKey(auth, userId);
+ final ProviderClientRecord pr = mProviderMap.get(key);
if (pr == null) {
return null;
}
@@ -4481,17 +4703,20 @@ public final class ActivityThread {
}
private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
- ContentProvider localProvider,IActivityManager.ContentProviderHolder holder) {
- String names[] = PATTERN_SEMICOLON.split(holder.info.authority);
- ProviderClientRecord pcr = new ProviderClientRecord(names, provider,
- localProvider, holder);
- for (int i = 0; i < names.length; i++) {
- ProviderClientRecord existing = mProviderMap.get(names[i]);
+ ContentProvider localProvider, IActivityManager.ContentProviderHolder holder) {
+ final String auths[] = PATTERN_SEMICOLON.split(holder.info.authority);
+ final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
+
+ final ProviderClientRecord pcr = new ProviderClientRecord(
+ auths, provider, localProvider, holder);
+ for (String auth : auths) {
+ final ProviderKey key = new ProviderKey(auth, userId);
+ final ProviderClientRecord existing = mProviderMap.get(key);
if (existing != null) {
Slog.w(TAG, "Content provider " + pcr.mHolder.info.name
- + " already published as " + names[i]);
+ + " already published as " + auth);
} else {
- mProviderMap.put(names[i], pcr);
+ mProviderMap.put(key, pcr);
}
}
return pcr;
@@ -4643,7 +4868,8 @@ public final class ActivityThread {
ensureJitEnabled();
}
});
- android.ddm.DdmHandleAppName.setAppName("<pre-initialized>");
+ android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
+ UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
IActivityManager mgr = ActivityManagerNative.getDefault();
try {
@@ -4654,7 +4880,8 @@ public final class ActivityThread {
} else {
// Don't set application object here -- if the system crashes,
// we can't display an alert, we just want to die die die.
- android.ddm.DdmHandleAppName.setAppName("system_process");
+ android.ddm.DdmHandleAppName.setAppName("system_process",
+ UserHandle.myUserId());
try {
mInstrumentation = new Instrumentation();
ContextImpl context = new ContextImpl();
@@ -4668,7 +4895,10 @@ public final class ActivityThread {
"Unable to instantiate Application():" + e.toString(), e);
}
}
-
+
+ // add dropbox logging to libcore
+ DropBox.setReporter(new DropBoxReporter());
+
ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {
public void onConfigurationChanged(Configuration newConfig) {
synchronized (mPackages) {
@@ -4717,6 +4947,32 @@ public final class ActivityThread {
}
}
+ private static class EventLoggingReporter implements EventLogger.Reporter {
+ @Override
+ public void report (int code, Object... list) {
+ EventLog.writeEvent(code, list);
+ }
+ }
+
+ private class DropBoxReporter implements DropBox.Reporter {
+
+ private DropBoxManager dropBox;
+
+ public DropBoxReporter() {
+ dropBox = (DropBoxManager) getSystemContext().getSystemService(Context.DROPBOX_SERVICE);
+ }
+
+ @Override
+ public void addData(String tag, byte[] data, int flags) {
+ dropBox.addData(tag, data, flags);
+ }
+
+ @Override
+ public void addText(String tag, String data) {
+ dropBox.addText(tag, data);
+ }
+ }
+
public static void main(String[] args) {
SamplingProfilerIntegration.start();
@@ -4725,6 +4981,11 @@ public final class ActivityThread {
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
+ Environment.initForCurrentUser();
+
+ // Set the reporter for event logging in libcore
+ EventLogger.setReporter(new EventLoggingReporter());
+
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index e37b3fa..6ab2bd1 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -110,8 +110,9 @@ public class AlertDialog extends Dialog implements DialogInterface {
this(context, theme, true);
}
- AlertDialog(Context context, int theme, boolean createContextWrapper) {
- super(context, resolveDialogTheme(context, theme), createContextWrapper);
+ AlertDialog(Context context, int theme, boolean createThemeContextWrapper) {
+ super(context, resolveDialogTheme(context, theme), createThemeContextWrapper);
+
mWindow.alwaysReadCloseOnTouchAttr();
mAlert = new AlertController(getContext(), this, getWindow());
}
@@ -566,7 +567,14 @@ public class AlertDialog extends Dialog implements DialogInterface {
/**
* Sets the callback that will be called if the dialog is canceled.
+ *
+ * <p>Even in a cancelable dialog, the dialog may be dismissed for reasons other than
+ * being canceled or one of the supplied choices being selected.
+ * If you are interested in listening for all cases where the dialog is dismissed
+ * and not just when it is canceled, see
+ * {@link #setOnDismissListener(android.content.DialogInterface.OnDismissListener) setOnDismissListener}.</p>
* @see #setCancelable(boolean)
+ * @see #setOnDismissListener(android.content.DialogInterface.OnDismissListener)
*
* @return This Builder object to allow for chaining of calls to set methods
*/
@@ -576,6 +584,16 @@ public class AlertDialog extends Dialog implements DialogInterface {
}
/**
+ * Sets the callback that will be called when the dialog is dismissed for any reason.
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
+ */
+ public Builder setOnDismissListener(OnDismissListener onDismissListener) {
+ P.mOnDismissListener = onDismissListener;
+ return this;
+ }
+
+ /**
* Sets the callback that will be called if a key is dispatched to the dialog.
*
* @return This Builder object to allow for chaining of calls to set methods
@@ -917,6 +935,7 @@ public class AlertDialog extends Dialog implements DialogInterface {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
+ dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index ebf4261..954476d 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -158,8 +158,8 @@ public class ApplicationErrorReport implements Parcelable {
public static ComponentName getErrorReportReceiver(Context context,
String packageName, int appFlags) {
// check if error reporting is enabled in secure settings
- int enabled = Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.SEND_ACTION_APP_ERROR, 0);
+ int enabled = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.SEND_ACTION_APP_ERROR, 0);
if (enabled == 0) {
return null;
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 191a696..7431765 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -41,8 +41,8 @@ import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
-import android.content.pm.UserInfo;
import android.content.pm.ManifestDigest;
+import android.content.pm.VerificationParams;
import android.content.pm.VerifierDeviceIdentity;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -50,8 +50,8 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Process;
import android.os.RemoteException;
-import android.os.UserId;
import android.util.Log;
+import android.view.Display;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -69,7 +69,7 @@ final class ApplicationPackageManager extends PackageManager {
public PackageInfo getPackageInfo(String packageName, int flags)
throws NameNotFoundException {
try {
- PackageInfo pi = mPM.getPackageInfo(packageName, flags, UserId.myUserId());
+ PackageInfo pi = mPM.getPackageInfo(packageName, flags, mContext.getUserId());
if (pi != null) {
return pi;
}
@@ -199,7 +199,7 @@ final class ApplicationPackageManager extends PackageManager {
public ApplicationInfo getApplicationInfo(String packageName, int flags)
throws NameNotFoundException {
try {
- ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags, UserId.myUserId());
+ ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags, mContext.getUserId());
if (ai != null) {
return ai;
}
@@ -214,7 +214,7 @@ final class ApplicationPackageManager extends PackageManager {
public ActivityInfo getActivityInfo(ComponentName className, int flags)
throws NameNotFoundException {
try {
- ActivityInfo ai = mPM.getActivityInfo(className, flags, UserId.myUserId());
+ ActivityInfo ai = mPM.getActivityInfo(className, flags, mContext.getUserId());
if (ai != null) {
return ai;
}
@@ -229,7 +229,7 @@ final class ApplicationPackageManager extends PackageManager {
public ActivityInfo getReceiverInfo(ComponentName className, int flags)
throws NameNotFoundException {
try {
- ActivityInfo ai = mPM.getReceiverInfo(className, flags, UserId.myUserId());
+ ActivityInfo ai = mPM.getReceiverInfo(className, flags, mContext.getUserId());
if (ai != null) {
return ai;
}
@@ -244,7 +244,7 @@ final class ApplicationPackageManager extends PackageManager {
public ServiceInfo getServiceInfo(ComponentName className, int flags)
throws NameNotFoundException {
try {
- ServiceInfo si = mPM.getServiceInfo(className, flags, UserId.myUserId());
+ ServiceInfo si = mPM.getServiceInfo(className, flags, mContext.getUserId());
if (si != null) {
return si;
}
@@ -259,7 +259,7 @@ final class ApplicationPackageManager extends PackageManager {
public ProviderInfo getProviderInfo(ComponentName className, int flags)
throws NameNotFoundException {
try {
- ProviderInfo pi = mPM.getProviderInfo(className, flags, UserId.myUserId());
+ ProviderInfo pi = mPM.getProviderInfo(className, flags, mContext.getUserId());
if (pi != null) {
return pi;
}
@@ -404,6 +404,12 @@ final class ApplicationPackageManager extends PackageManager {
@SuppressWarnings("unchecked")
@Override
public List<PackageInfo> getInstalledPackages(int flags) {
+ return getInstalledPackages(flags, mContext.getUserId());
+ }
+
+ /** @hide */
+ @Override
+ public List<PackageInfo> getInstalledPackages(int flags, int userId) {
try {
final List<PackageInfo> packageInfos = new ArrayList<PackageInfo>();
PackageInfo lastItem = null;
@@ -411,7 +417,7 @@ final class ApplicationPackageManager extends PackageManager {
do {
final String lastKey = lastItem != null ? lastItem.packageName : null;
- slice = mPM.getInstalledPackages(flags, lastKey);
+ slice = mPM.getInstalledPackages(flags, lastKey, userId);
lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR);
} while (!slice.isLastSlice());
@@ -424,7 +430,7 @@ final class ApplicationPackageManager extends PackageManager {
@SuppressWarnings("unchecked")
@Override
public List<ApplicationInfo> getInstalledApplications(int flags) {
- int userId = UserId.getUserId(Process.myUid());
+ final int userId = mContext.getUserId();
try {
final List<ApplicationInfo> applicationInfos = new ArrayList<ApplicationInfo>();
ApplicationInfo lastItem = null;
@@ -444,11 +450,17 @@ final class ApplicationPackageManager extends PackageManager {
@Override
public ResolveInfo resolveActivity(Intent intent, int flags) {
+ return resolveActivityAsUser(intent, flags, mContext.getUserId());
+ }
+
+ @Override
+ public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
try {
return mPM.resolveIntent(
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- flags, UserId.myUserId());
+ flags,
+ userId);
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
@@ -457,12 +469,19 @@ final class ApplicationPackageManager extends PackageManager {
@Override
public List<ResolveInfo> queryIntentActivities(Intent intent,
int flags) {
+ return queryIntentActivitiesAsUser(intent, flags, mContext.getUserId());
+ }
+
+ /** @hide Same as above but for a specific user */
+ @Override
+ public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
+ int flags, int userId) {
try {
return mPM.queryIntentActivities(
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
flags,
- UserId.myUserId());
+ userId);
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
@@ -494,56 +513,69 @@ final class ApplicationPackageManager extends PackageManager {
try {
return mPM.queryIntentActivityOptions(caller, specifics,
specificTypes, intent, intent.resolveTypeIfNeeded(resolver),
- flags, UserId.myUserId());
+ flags, mContext.getUserId());
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
}
+ /**
+ * @hide
+ */
@Override
- public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+ public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags, int userId) {
try {
return mPM.queryIntentReceivers(
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
flags,
- UserId.myUserId());
+ userId);
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
}
@Override
+ public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+ return queryBroadcastReceivers(intent, flags, mContext.getUserId());
+ }
+
+ @Override
public ResolveInfo resolveService(Intent intent, int flags) {
try {
return mPM.resolveService(
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
flags,
- UserId.myUserId());
+ mContext.getUserId());
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
}
@Override
- public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {
+ public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
try {
return mPM.queryIntentServices(
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
flags,
- UserId.myUserId());
+ userId);
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
}
@Override
+ public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {
+ return queryIntentServicesAsUser(intent, flags, mContext.getUserId());
+ }
+
+ @Override
public ProviderInfo resolveContentProvider(String name,
int flags) {
try {
- return mPM.resolveContentProvider(name, flags, UserId.myUserId());
+ return mPM.resolveContentProvider(name, flags, mContext.getUserId());
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
@@ -712,8 +744,8 @@ final class ApplicationPackageManager extends PackageManager {
return mContext.mMainThread.getSystemContext().getResources();
}
Resources r = mContext.mMainThread.getTopLevelResources(
- app.uid == Process.myUid() ? app.sourceDir
- : app.publicSourceDir, mContext.mPackageInfo);
+ app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir,
+ Display.DEFAULT_DISPLAY, null, mContext.mPackageInfo);
if (r != null) {
return r;
}
@@ -726,6 +758,28 @@ final class ApplicationPackageManager extends PackageManager {
getApplicationInfo(appPackageName, 0));
}
+ /** @hide */
+ @Override
+ public Resources getResourcesForApplicationAsUser(String appPackageName, int userId)
+ throws NameNotFoundException {
+ if (userId < 0) {
+ throw new IllegalArgumentException(
+ "Call does not support special user #" + userId);
+ }
+ if ("system".equals(appPackageName)) {
+ return mContext.mMainThread.getSystemContext().getResources();
+ }
+ try {
+ ApplicationInfo ai = mPM.getApplicationInfo(appPackageName, 0, userId);
+ if (ai != null) {
+ return getResourcesForApplication(ai);
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ throw new NameNotFoundException("Package " + appPackageName + " doesn't exist");
+ }
+
int mCachedSafeMode = -1;
@Override public boolean isSafeMode() {
try {
@@ -984,6 +1038,33 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
+ public void installPackageWithVerificationAndEncryption(Uri packageURI,
+ IPackageInstallObserver observer, int flags, String installerPackageName,
+ VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
+ try {
+ mPM.installPackageWithVerificationAndEncryption(packageURI, observer, flags,
+ installerPackageName, verificationParams, encryptionParams);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ @Override
+ public int installExistingPackage(String packageName)
+ throws NameNotFoundException {
+ try {
+ int res = mPM.installExistingPackage(packageName);
+ if (res == INSTALL_FAILED_INVALID_URI) {
+ throw new NameNotFoundException("Package " + packageName + " doesn't exist");
+ }
+ return res;
+ } catch (RemoteException e) {
+ // Should never happen!
+ throw new NameNotFoundException("Package " + packageName + " doesn't exist");
+ }
+ }
+
+ @Override
public void verifyPendingInstall(int id, int response) {
try {
mPM.verifyPendingInstall(id, response);
@@ -993,6 +1074,16 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
+ public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
+ long millisecondsToDelay) {
+ try {
+ mPM.extendVerificationTimeout(id, verificationCodeAtTimeout, millisecondsToDelay);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ @Override
public void setInstallerPackageName(String targetPackage,
String installerPackageName) {
try {
@@ -1033,7 +1124,7 @@ final class ApplicationPackageManager extends PackageManager {
public void clearApplicationUserData(String packageName,
IPackageDataObserver observer) {
try {
- mPM.clearApplicationUserData(packageName, observer, UserId.myUserId());
+ mPM.clearApplicationUserData(packageName, observer, mContext.getUserId());
} catch (RemoteException e) {
// Should never happen!
}
@@ -1066,10 +1157,10 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
- public void getPackageSizeInfo(String packageName,
- IPackageStatsObserver observer) {
+ public void getPackageSizeInfo(String packageName, int userHandle,
+ IPackageStatsObserver observer) {
try {
- mPM.getPackageSizeInfo(packageName, observer);
+ mPM.getPackageSizeInfo(packageName, userHandle, observer);
} catch (RemoteException e) {
// Should never happen!
}
@@ -1106,7 +1197,17 @@ final class ApplicationPackageManager extends PackageManager {
public void addPreferredActivity(IntentFilter filter,
int match, ComponentName[] set, ComponentName activity) {
try {
- mPM.addPreferredActivity(filter, match, set, activity);
+ mPM.addPreferredActivity(filter, match, set, activity, mContext.getUserId());
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ @Override
+ public void addPreferredActivity(IntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity, int userId) {
+ try {
+ mPM.addPreferredActivity(filter, match, set, activity, userId);
} catch (RemoteException e) {
// Should never happen!
}
@@ -1146,7 +1247,7 @@ final class ApplicationPackageManager extends PackageManager {
public void setComponentEnabledSetting(ComponentName componentName,
int newState, int flags) {
try {
- mPM.setComponentEnabledSetting(componentName, newState, flags, UserId.myUserId());
+ mPM.setComponentEnabledSetting(componentName, newState, flags, mContext.getUserId());
} catch (RemoteException e) {
// Should never happen!
}
@@ -1155,7 +1256,7 @@ final class ApplicationPackageManager extends PackageManager {
@Override
public int getComponentEnabledSetting(ComponentName componentName) {
try {
- return mPM.getComponentEnabledSetting(componentName, UserId.myUserId());
+ return mPM.getComponentEnabledSetting(componentName, mContext.getUserId());
} catch (RemoteException e) {
// Should never happen!
}
@@ -1166,7 +1267,7 @@ final class ApplicationPackageManager extends PackageManager {
public void setApplicationEnabledSetting(String packageName,
int newState, int flags) {
try {
- mPM.setApplicationEnabledSetting(packageName, newState, flags, UserId.myUserId());
+ mPM.setApplicationEnabledSetting(packageName, newState, flags, mContext.getUserId());
} catch (RemoteException e) {
// Should never happen!
}
@@ -1175,86 +1276,13 @@ final class ApplicationPackageManager extends PackageManager {
@Override
public int getApplicationEnabledSetting(String packageName) {
try {
- return mPM.getApplicationEnabledSetting(packageName, UserId.myUserId());
+ return mPM.getApplicationEnabledSetting(packageName, mContext.getUserId());
} catch (RemoteException e) {
// Should never happen!
}
return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
}
- // Multi-user support
-
- /**
- * @hide
- */
- @Override
- public UserInfo createUser(String name, int flags) {
- try {
- return mPM.createUser(name, flags);
- } catch (RemoteException e) {
- // Should never happen!
- }
- return null;
- }
-
- /**
- * @hide
- */
- @Override
- public List<UserInfo> getUsers() {
- try {
- return mPM.getUsers();
- } catch (RemoteException re) {
- ArrayList<UserInfo> users = new ArrayList<UserInfo>();
- UserInfo primary = new UserInfo(0, "Root!", UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
- users.add(primary);
- return users;
- }
- }
-
- /**
- * @hide
- */
- @Override
- public UserInfo getUser(int userId) {
- try {
- return mPM.getUser(userId);
- } catch (RemoteException re) {
- return null;
- }
- }
-
- /**
- * @hide
- */
- @Override
- public boolean removeUser(int id) {
- try {
- return mPM.removeUser(id);
- } catch (RemoteException e) {
- return false;
- }
- }
-
- /**
- * @hide
- */
- @Override
- public void updateUserName(int id, String name) {
- try {
- mPM.updateUserName(id, name);
- } catch (RemoteException re) {
- }
- }
-
- /**
- * @hide
- */
- @Override
- public void updateUserFlags(int id, int flags) {
- // TODO:
- }
-
/**
* @hide
*/
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 8e6278d..63aa5f9 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -193,8 +193,9 @@ public abstract class ApplicationThreadNative extends Binder
String resultData = data.readString();
Bundle resultExtras = data.readBundle();
boolean sync = data.readInt() != 0;
+ int sendingUser = data.readInt();
scheduleReceiver(intent, info, compatInfo, resultCode, resultData,
- resultExtras, sync);
+ resultExtras, sync, sendingUser);
return true;
}
@@ -378,8 +379,9 @@ public abstract class ApplicationThreadNative extends Binder
Bundle extras = data.readBundle();
boolean ordered = data.readInt() != 0;
boolean sticky = data.readInt() != 0;
+ int sendingUser = data.readInt();
scheduleRegisteredReceiver(receiver, intent,
- resultCode, dataStr, extras, ordered, sticky);
+ resultCode, dataStr, extras, ordered, sticky, sendingUser);
return true;
}
@@ -755,7 +757,7 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String resultData,
- Bundle map, boolean sync) throws RemoteException {
+ Bundle map, boolean sync, int sendingUser) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
intent.writeToParcel(data, 0);
@@ -765,6 +767,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeString(resultData);
data.writeBundle(map);
data.writeInt(sync ? 1 : 0);
+ data.writeInt(sendingUser);
mRemote.transact(SCHEDULE_RECEIVER_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
@@ -991,8 +994,8 @@ class ApplicationThreadProxy implements IApplicationThread {
}
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
- int resultCode, String dataStr, Bundle extras, boolean ordered, boolean sticky)
- throws RemoteException {
+ int resultCode, String dataStr, Bundle extras, boolean ordered,
+ boolean sticky, int sendingUser) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(receiver.asBinder());
@@ -1002,6 +1005,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeBundle(extras);
data.writeInt(ordered ? 1 : 0);
data.writeInt(sticky ? 1 : 0);
+ data.writeInt(sendingUser);
mRemote.transact(SCHEDULE_REGISTERED_RECEIVER_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 96814b7..1b1d341 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -20,6 +20,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
+import android.util.LogWriter;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -94,11 +95,12 @@ final class BackStackState implements Parcelable {
public BackStackRecord instantiate(FragmentManagerImpl fm) {
BackStackRecord bse = new BackStackRecord(fm);
int pos = 0;
+ int num = 0;
while (pos < mOps.length) {
BackStackRecord.Op op = new BackStackRecord.Op();
op.cmd = mOps[pos++];
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
- "BSE " + bse + " set base fragment #" + mOps[pos]);
+ "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
int findex = mOps[pos++];
if (findex >= 0) {
Fragment f = fm.mActive.get(findex);
@@ -115,12 +117,13 @@ final class BackStackState implements Parcelable {
op.removed = new ArrayList<Fragment>(N);
for (int i=0; i<N; i++) {
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
- "BSE " + bse + " set remove fragment #" + mOps[pos]);
+ "Instantiate " + bse + " set remove fragment #" + mOps[pos]);
Fragment r = fm.mActive.get(mOps[pos++]);
op.removed.add(r);
}
}
bse.addOp(op);
+ num++;
}
bse.mTransition = mTransition;
bse.mTransitionStyle = mTransitionStyle;
@@ -168,7 +171,7 @@ final class BackStackState implements Parcelable {
*/
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable {
- static final String TAG = "BackStackEntry";
+ static final String TAG = FragmentManagerImpl.TAG;
final FragmentManagerImpl mManager;
@@ -206,46 +209,69 @@ final class BackStackRecord extends FragmentTransaction implements
boolean mAllowAddToBackStack = true;
String mName;
boolean mCommitted;
- int mIndex;
+ int mIndex = -1;
int mBreadCrumbTitleRes;
CharSequence mBreadCrumbTitleText;
int mBreadCrumbShortTitleRes;
CharSequence mBreadCrumbShortTitleText;
- public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
- writer.print(prefix); writer.print("mName="); writer.print(mName);
- writer.print(" mIndex="); writer.print(mIndex);
- writer.print(" mCommitted="); writer.println(mCommitted);
- if (mTransition != FragmentTransaction.TRANSIT_NONE) {
- writer.print(prefix); writer.print("mTransition=#");
- writer.print(Integer.toHexString(mTransition));
- writer.print(" mTransitionStyle=#");
- writer.println(Integer.toHexString(mTransitionStyle));
- }
- if (mEnterAnim != 0 || mExitAnim !=0) {
- writer.print(prefix); writer.print("mEnterAnim=#");
- writer.print(Integer.toHexString(mEnterAnim));
- writer.print(" mExitAnim=#");
- writer.println(Integer.toHexString(mExitAnim));
- }
- if (mPopEnterAnim != 0 || mPopExitAnim !=0) {
- writer.print(prefix); writer.print("mPopEnterAnim=#");
- writer.print(Integer.toHexString(mPopEnterAnim));
- writer.print(" mPopExitAnim=#");
- writer.println(Integer.toHexString(mPopExitAnim));
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("BackStackEntry{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ if (mIndex >= 0) {
+ sb.append(" #");
+ sb.append(mIndex);
}
- if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
- writer.print(prefix); writer.print("mBreadCrumbTitleRes=#");
- writer.print(Integer.toHexString(mBreadCrumbTitleRes));
- writer.print(" mBreadCrumbTitleText=");
- writer.println(mBreadCrumbTitleText);
+ if (mName != null) {
+ sb.append(" ");
+ sb.append(mName);
}
- if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
- writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#");
- writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
- writer.print(" mBreadCrumbShortTitleText=");
- writer.println(mBreadCrumbShortTitleText);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ dump(prefix, writer, true);
+ }
+
+ void dump(String prefix, PrintWriter writer, boolean full) {
+ if (full) {
+ writer.print(prefix); writer.print("mName="); writer.print(mName);
+ writer.print(" mIndex="); writer.print(mIndex);
+ writer.print(" mCommitted="); writer.println(mCommitted);
+ if (mTransition != FragmentTransaction.TRANSIT_NONE) {
+ writer.print(prefix); writer.print("mTransition=#");
+ writer.print(Integer.toHexString(mTransition));
+ writer.print(" mTransitionStyle=#");
+ writer.println(Integer.toHexString(mTransitionStyle));
+ }
+ if (mEnterAnim != 0 || mExitAnim !=0) {
+ writer.print(prefix); writer.print("mEnterAnim=#");
+ writer.print(Integer.toHexString(mEnterAnim));
+ writer.print(" mExitAnim=#");
+ writer.println(Integer.toHexString(mExitAnim));
+ }
+ if (mPopEnterAnim != 0 || mPopExitAnim !=0) {
+ writer.print(prefix); writer.print("mPopEnterAnim=#");
+ writer.print(Integer.toHexString(mPopEnterAnim));
+ writer.print(" mPopExitAnim=#");
+ writer.println(Integer.toHexString(mPopExitAnim));
+ }
+ if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
+ writer.print(prefix); writer.print("mBreadCrumbTitleRes=#");
+ writer.print(Integer.toHexString(mBreadCrumbTitleRes));
+ writer.print(" mBreadCrumbTitleText=");
+ writer.println(mBreadCrumbTitleText);
+ }
+ if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
+ writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#");
+ writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
+ writer.print(" mBreadCrumbShortTitleText=");
+ writer.println(mBreadCrumbShortTitleText);
+ }
}
if (mHead != null) {
@@ -254,21 +280,34 @@ final class BackStackRecord extends FragmentTransaction implements
Op op = mHead;
int num = 0;
while (op != null) {
- writer.print(prefix); writer.print(" Op #"); writer.print(num);
- writer.println(":");
- writer.print(innerPrefix); writer.print("cmd="); writer.print(op.cmd);
- writer.print(" fragment="); writer.println(op.fragment);
- if (op.enterAnim != 0 || op.exitAnim != 0) {
- writer.print(prefix); writer.print("enterAnim=#");
- writer.print(Integer.toHexString(op.enterAnim));
- writer.print(" exitAnim=#");
- writer.println(Integer.toHexString(op.exitAnim));
+ String cmdStr;
+ switch (op.cmd) {
+ case OP_NULL: cmdStr="NULL"; break;
+ case OP_ADD: cmdStr="ADD"; break;
+ case OP_REPLACE: cmdStr="REPLACE"; break;
+ case OP_REMOVE: cmdStr="REMOVE"; break;
+ case OP_HIDE: cmdStr="HIDE"; break;
+ case OP_SHOW: cmdStr="SHOW"; break;
+ case OP_DETACH: cmdStr="DETACH"; break;
+ case OP_ATTACH: cmdStr="ATTACH"; break;
+ default: cmdStr="cmd=" + op.cmd; break;
}
- if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
- writer.print(prefix); writer.print("popEnterAnim=#");
- writer.print(Integer.toHexString(op.popEnterAnim));
- writer.print(" popExitAnim=#");
- writer.println(Integer.toHexString(op.popExitAnim));
+ writer.print(prefix); writer.print(" Op #"); writer.print(num);
+ writer.print(": "); writer.print(cmdStr);
+ writer.print(" "); writer.println(op.fragment);
+ if (full) {
+ if (op.enterAnim != 0 || op.exitAnim != 0) {
+ writer.print(innerPrefix); writer.print("enterAnim=#");
+ writer.print(Integer.toHexString(op.enterAnim));
+ writer.print(" exitAnim=#");
+ writer.println(Integer.toHexString(op.exitAnim));
+ }
+ if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
+ writer.print(innerPrefix); writer.print("popEnterAnim=#");
+ writer.print(Integer.toHexString(op.popEnterAnim));
+ writer.print(" popExitAnim=#");
+ writer.println(Integer.toHexString(op.popExitAnim));
+ }
}
if (op.removed != null && op.removed.size() > 0) {
for (int i=0; i<op.removed.size(); i++) {
@@ -276,14 +315,17 @@ final class BackStackRecord extends FragmentTransaction implements
if (op.removed.size() == 1) {
writer.print("Removed: ");
} else {
- writer.println("Removed:");
- writer.print(innerPrefix); writer.print(" #"); writer.print(num);
+ if (i == 0) {
+ writer.println("Removed:");
+ }
+ writer.print(innerPrefix); writer.print(" #"); writer.print(i);
writer.print(": ");
}
writer.println(op.removed.get(i));
}
}
op = op.next;
+ num++;
}
}
}
@@ -538,7 +580,12 @@ final class BackStackRecord extends FragmentTransaction implements
int commitInternal(boolean allowStateLoss) {
if (mCommitted) throw new IllegalStateException("commit already called");
- if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Commit: " + this);
+ if (FragmentManagerImpl.DEBUG) {
+ Log.v(TAG, "Commit: " + this);
+ LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
+ PrintWriter pw = new PrintWriter(logw);
+ dump(" ", null, pw, null);
+ }
mCommitted = true;
if (mAddToBackStack) {
mIndex = mManager.allocBackStackIndex(this);
@@ -641,7 +688,12 @@ final class BackStackRecord extends FragmentTransaction implements
}
public void popFromBackStack(boolean doStateMove) {
- if (FragmentManagerImpl.DEBUG) Log.v(TAG, "popFromBackStack: " + this);
+ if (FragmentManagerImpl.DEBUG) {
+ Log.v(TAG, "popFromBackStack: " + this);
+ LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
+ PrintWriter pw = new PrintWriter(logw);
+ dump(" ", null, pw, null);
+ }
bumpBackStackNesting(-1);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 411f6d0..c41405b 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -17,7 +17,9 @@
package android.app;
import com.android.internal.policy.PolicyManager;
+import com.android.internal.util.Preconditions;
+import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -34,8 +36,10 @@ import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
@@ -46,6 +50,7 @@ import android.hardware.ISerialManager;
import android.hardware.SensorManager;
import android.hardware.SerialManager;
import android.hardware.SystemSensorManager;
+import android.hardware.display.DisplayManager;
import android.hardware.input.IInputManager;
import android.hardware.input.InputManager;
import android.hardware.usb.IUsbManager;
@@ -78,19 +83,23 @@ import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.IPowerManager;
+import android.os.IUserManager;
import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.UserId;
+import android.os.UserHandle;
import android.os.SystemVibrator;
+import android.os.UserManager;
import android.os.storage.StorageManager;
import android.telephony.TelephonyManager;
import android.content.ClipboardManager;
import android.util.AndroidRuntimeException;
import android.util.Log;
+import android.view.CompatibilityInfoHolder;
import android.view.ContextThemeWrapper;
+import android.view.Display;
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputMethodManager;
@@ -122,21 +131,33 @@ class ReceiverRestrictedContext extends ContextWrapper {
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
- throw new ReceiverCallNotAllowedException(
- "IntentReceiver components are not allowed to register to receive intents");
- //ex.fillInStackTrace();
- //Log.e("IntentReceiver", ex.getMessage(), ex);
- //return mContext.registerReceiver(receiver, filter, broadcastPermission,
- // scheduler);
+ if (receiver == null) {
+ // Allow retrieving current sticky broadcast; this is safe since we
+ // aren't actually registering a receiver.
+ return super.registerReceiver(null, filter, broadcastPermission, scheduler);
+ } else {
+ throw new ReceiverCallNotAllowedException(
+ "BroadcastReceiver components are not allowed to register to receive intents");
+ }
+ }
+
+ @Override
+ public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ if (receiver == null) {
+ // Allow retrieving current sticky broadcast; this is safe since we
+ // aren't actually registering a receiver.
+ return super.registerReceiverAsUser(null, user, filter, broadcastPermission, scheduler);
+ } else {
+ throw new ReceiverCallNotAllowedException(
+ "BroadcastReceiver components are not allowed to register to receive intents");
+ }
}
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
throw new ReceiverCallNotAllowedException(
- "IntentReceiver components are not allowed to bind to services");
- //ex.fillInStackTrace();
- //Log.e("IntentReceiver", ex.getMessage(), ex);
- //return mContext.bindService(service, interfaceName, conn, flags);
+ "BroadcastReceiver components are not allowed to bind to services");
}
}
@@ -161,8 +182,10 @@ class ContextImpl extends Context {
private int mThemeResource = 0;
private Resources.Theme mTheme = null;
private PackageManager mPackageManager;
+ private Display mDisplay; // may be null if default display
private Context mReceiverRestrictedContext = null;
private boolean mRestricted;
+ private UserHandle mUser;
private final Object mSync = new Object();
@@ -294,6 +317,11 @@ class ContextImpl extends Context {
return new MediaRouter(ctx);
}});
+ registerService(BLUETOOTH_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ return BluetoothAdapter.getDefaultAdapter();
+ }});
+
registerService(CLIPBOARD_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return new ClipboardManager(ctx.getOuterContext(),
@@ -337,6 +365,12 @@ class ContextImpl extends Context {
return InputManager.getInstance();
}});
+ registerService(DISPLAY_SERVICE, new ServiceFetcher() {
+ @Override
+ public Object createService(ContextImpl ctx) {
+ return new DisplayManager(ctx.getOuterContext());
+ }});
+
registerService(INPUT_METHOD_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return InputMethodManager.getInstance(ctx);
@@ -403,7 +437,8 @@ class ContextImpl extends Context {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(POWER_SERVICE);
IPowerManager service = IPowerManager.Stub.asInterface(b);
- return new PowerManager(service, ctx.mMainThread.getHandler());
+ return new PowerManager(ctx.getOuterContext(),
+ service, ctx.mMainThread.getHandler());
}});
registerService(SEARCH_SERVICE, new ServiceFetcher() {
@@ -471,7 +506,7 @@ class ContextImpl extends Context {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(WIFI_SERVICE);
IWifiManager service = IWifiManager.Stub.asInterface(b);
- return new WifiManager(service, ctx.mMainThread.getHandler());
+ return new WifiManager(ctx.getOuterContext(), service);
}});
registerService(WIFI_P2P_SERVICE, new ServiceFetcher() {
@@ -483,8 +518,21 @@ class ContextImpl extends Context {
registerService(WINDOW_SERVICE, new ServiceFetcher() {
public Object getService(ContextImpl ctx) {
- return WindowManagerImpl.getDefault(ctx.mPackageInfo.mCompatibilityInfo);
+ Display display = ctx.mDisplay;
+ if (display == null) {
+ DisplayManager dm = (DisplayManager)ctx.getOuterContext().getSystemService(
+ Context.DISPLAY_SERVICE);
+ display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+ }
+ return new WindowManagerImpl(display);
}});
+
+ registerService(USER_SERVICE, new ServiceFetcher() {
+ public Object getService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(USER_SERVICE);
+ IUserManager service = IUserManager.Stub.asInterface(b);
+ return new UserManager(ctx, service);
+ }});
}
static ContextImpl getImpl(Context context) {
@@ -503,7 +551,7 @@ class ContextImpl extends Context {
@Override
public AssetManager getAssets() {
- return mResources.getAssets();
+ return getResources().getAssets();
}
@Override
@@ -743,7 +791,7 @@ class ContextImpl extends Context {
}
if (!mCacheDir.exists()) {
if(!mCacheDir.mkdirs()) {
- Log.w(TAG, "Unable to create cache directory");
+ Log.w(TAG, "Unable to create cache directory " + mCacheDir.getAbsolutePath());
return null;
}
FileUtils.setPermissions(
@@ -880,6 +928,12 @@ class ContextImpl extends Context {
startActivity(intent, null);
}
+ /** @hide */
+ @Override
+ public void startActivityAsUser(Intent intent, UserHandle user) {
+ startActivityAsUser(intent, null, user);
+ }
+
@Override
public void startActivity(Intent intent, Bundle options) {
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
@@ -893,11 +947,38 @@ class ContextImpl extends Context {
(Activity)null, intent, -1, options);
}
+ /** @hide */
+ @Override
+ public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+ try {
+ ActivityManagerNative.getDefault().startActivityAsUser(
+ mMainThread.getApplicationThread(), intent,
+ intent.resolveTypeIfNeeded(getContentResolver()),
+ null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, null, options,
+ user.getIdentifier());
+ } catch (RemoteException re) {
+ }
+ }
+
@Override
public void startActivities(Intent[] intents) {
startActivities(intents, null);
}
+ /** @hide */
+ @Override
+ public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+ if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+ throw new AndroidRuntimeException(
+ "Calling startActivities() from outside of an Activity "
+ + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
+ + " Is this really what you want?");
+ }
+ mMainThread.getInstrumentation().execStartActivitiesAsUser(
+ getOuterContext(), mMainThread.getApplicationThread(), null,
+ (Activity)null, intents, options, userHandle.getIdentifier());
+ }
+
@Override
public void startActivities(Intent[] intents, Bundle options) {
if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
@@ -948,56 +1029,101 @@ class ContextImpl extends Context {
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, null, false, false,
- Binder.getOrigCallingUser());
+ getUserId());
} catch (RemoteException e) {
}
}
- /** @hide */
@Override
- public void sendBroadcast(Intent intent, int userId) {
+ public void sendBroadcast(Intent intent, String receiverPermission) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
intent.setAllowFds(false);
- ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(),
- intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false,
- userId);
+ ActivityManagerNative.getDefault().broadcastIntent(
+ mMainThread.getApplicationThread(), intent, resolvedType, null,
+ Activity.RESULT_OK, null, null, receiverPermission, false, false,
+ getUserId());
} catch (RemoteException e) {
}
}
@Override
- public void sendBroadcast(Intent intent, String receiverPermission) {
+ public void sendOrderedBroadcast(Intent intent,
+ String receiverPermission) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, receiverPermission, false, false,
- Binder.getOrigCallingUser());
+ Activity.RESULT_OK, null, null, receiverPermission, true, false,
+ getUserId());
} catch (RemoteException e) {
}
}
@Override
public void sendOrderedBroadcast(Intent intent,
+ String receiverPermission, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
+ IIntentReceiver rd = null;
+ if (resultReceiver != null) {
+ if (mPackageInfo != null) {
+ if (scheduler == null) {
+ scheduler = mMainThread.getHandler();
+ }
+ rd = mPackageInfo.getReceiverDispatcher(
+ resultReceiver, getOuterContext(), scheduler,
+ mMainThread.getInstrumentation(), false);
+ } else {
+ if (scheduler == null) {
+ scheduler = mMainThread.getHandler();
+ }
+ rd = new LoadedApk.ReceiverDispatcher(
+ resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver();
+ }
+ }
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+ try {
+ intent.setAllowFds(false);
+ ActivityManagerNative.getDefault().broadcastIntent(
+ mMainThread.getApplicationThread(), intent, resolvedType, rd,
+ initialCode, initialData, initialExtras, receiverPermission,
+ true, false, getUserId());
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+ try {
+ intent.setAllowFds(false);
+ ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(),
+ intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false,
+ user.getIdentifier());
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, receiverPermission, true, false,
- Binder.getOrigCallingUser());
+ Activity.RESULT_OK, null, null, receiverPermission, false, false,
+ user.getIdentifier());
} catch (RemoteException e) {
}
}
@Override
- public void sendOrderedBroadcast(Intent intent,
- String receiverPermission, BroadcastReceiver resultReceiver,
- Handler scheduler, int initialCode, String initialData,
- Bundle initialExtras) {
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras) {
IIntentReceiver rd = null;
if (resultReceiver != null) {
if (mPackageInfo != null) {
@@ -1021,7 +1147,7 @@ class ContextImpl extends Context {
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
initialCode, initialData, initialExtras, receiverPermission,
- true, false, Binder.getOrigCallingUser());
+ true, false, user.getIdentifier());
} catch (RemoteException e) {
}
}
@@ -1034,7 +1160,7 @@ class ContextImpl extends Context {
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, null, false, true,
- Binder.getOrigCallingUser());
+ getUserId());
} catch (RemoteException e) {
}
}
@@ -1067,7 +1193,7 @@ class ContextImpl extends Context {
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
initialCode, initialData, initialExtras, null,
- true, true, Binder.getOrigCallingUser());
+ true, true, getUserId());
} catch (RemoteException e) {
}
}
@@ -1082,7 +1208,67 @@ class ContextImpl extends Context {
try {
intent.setAllowFds(false);
ActivityManagerNative.getDefault().unbroadcastIntent(
- mMainThread.getApplicationThread(), intent, Binder.getOrigCallingUser());
+ mMainThread.getApplicationThread(), intent, getUserId());
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+ try {
+ intent.setAllowFds(false);
+ ActivityManagerNative.getDefault().broadcastIntent(
+ mMainThread.getApplicationThread(), intent, resolvedType, null,
+ Activity.RESULT_OK, null, null, null, false, true, user.getIdentifier());
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void sendStickyOrderedBroadcastAsUser(Intent intent,
+ UserHandle user, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
+ IIntentReceiver rd = null;
+ if (resultReceiver != null) {
+ if (mPackageInfo != null) {
+ if (scheduler == null) {
+ scheduler = mMainThread.getHandler();
+ }
+ rd = mPackageInfo.getReceiverDispatcher(
+ resultReceiver, getOuterContext(), scheduler,
+ mMainThread.getInstrumentation(), false);
+ } else {
+ if (scheduler == null) {
+ scheduler = mMainThread.getHandler();
+ }
+ rd = new LoadedApk.ReceiverDispatcher(
+ resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver();
+ }
+ }
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+ try {
+ intent.setAllowFds(false);
+ ActivityManagerNative.getDefault().broadcastIntent(
+ mMainThread.getApplicationThread(), intent, resolvedType, rd,
+ initialCode, initialData, initialExtras, null,
+ true, true, user.getIdentifier());
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) {
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+ if (resolvedType != null) {
+ intent = new Intent(intent);
+ intent.setDataAndType(intent.getData(), resolvedType);
+ }
+ try {
+ intent.setAllowFds(false);
+ ActivityManagerNative.getDefault().unbroadcastIntent(
+ mMainThread.getApplicationThread(), intent, user.getIdentifier());
} catch (RemoteException e) {
}
}
@@ -1095,11 +1281,18 @@ class ContextImpl extends Context {
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
- return registerReceiverInternal(receiver, filter, broadcastPermission,
- scheduler, getOuterContext());
+ return registerReceiverInternal(receiver, getUserId(),
+ filter, broadcastPermission, scheduler, getOuterContext());
}
- private Intent registerReceiverInternal(BroadcastReceiver receiver,
+ @Override
+ public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ return registerReceiverInternal(receiver, user.getIdentifier(),
+ filter, broadcastPermission, scheduler, getOuterContext());
+ }
+
+ private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
IntentFilter filter, String broadcastPermission,
Handler scheduler, Context context) {
IIntentReceiver rd = null;
@@ -1122,7 +1315,7 @@ class ContextImpl extends Context {
try {
return ActivityManagerNative.getDefault().registerReceiver(
mMainThread.getApplicationThread(), mBasePackageName,
- rd, filter, broadcastPermission);
+ rd, filter, broadcastPermission, userId);
} catch (RemoteException e) {
return null;
}
@@ -1144,11 +1337,21 @@ class ContextImpl extends Context {
@Override
public ComponentName startService(Intent service) {
+ return startServiceAsUser(service, mUser);
+ }
+
+ @Override
+ public boolean stopService(Intent service) {
+ return stopServiceAsUser(service, mUser);
+ }
+
+ @Override
+ public ComponentName startServiceAsUser(Intent service, UserHandle user) {
try {
service.setAllowFds(false);
ComponentName cn = ActivityManagerNative.getDefault().startService(
mMainThread.getApplicationThread(), service,
- service.resolveTypeIfNeeded(getContentResolver()));
+ service.resolveTypeIfNeeded(getContentResolver()), user.getIdentifier());
if (cn != null && cn.getPackageName().equals("!")) {
throw new SecurityException(
"Not allowed to start service " + service
@@ -1161,12 +1364,12 @@ class ContextImpl extends Context {
}
@Override
- public boolean stopService(Intent service) {
+ public boolean stopServiceAsUser(Intent service, UserHandle user) {
try {
service.setAllowFds(false);
int res = ActivityManagerNative.getDefault().stopService(
mMainThread.getApplicationThread(), service,
- service.resolveTypeIfNeeded(getContentResolver()));
+ service.resolveTypeIfNeeded(getContentResolver()), user.getIdentifier());
if (res < 0) {
throw new SecurityException(
"Not allowed to stop service " + service);
@@ -1180,13 +1383,16 @@ class ContextImpl extends Context {
@Override
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
- return bindService(service, conn, flags, UserId.getUserId(Process.myUid()));
+ return bindService(service, conn, flags, UserHandle.getUserId(Process.myUid()));
}
/** @hide */
@Override
- public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) {
+ public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) {
IServiceConnection sd;
+ if (conn == null) {
+ throw new IllegalArgumentException("connection is null");
+ }
if (mPackageInfo != null) {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
mMainThread.getHandler(), flags);
@@ -1204,7 +1410,7 @@ class ContextImpl extends Context {
int res = ActivityManagerNative.getDefault().bindService(
mMainThread.getApplicationThread(), getActivityToken(),
service, service.resolveTypeIfNeeded(getContentResolver()),
- sd, flags, userId);
+ sd, flags, userHandle);
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);
@@ -1217,6 +1423,9 @@ class ContextImpl extends Context {
@Override
public void unbindService(ServiceConnection conn) {
+ if (conn == null) {
+ throw new IllegalArgumentException("connection is null");
+ }
if (mPackageInfo != null) {
IServiceConnection sd = mPackageInfo.forgetServiceDispatcher(
getOuterContext(), conn);
@@ -1237,7 +1446,7 @@ class ContextImpl extends Context {
arguments.setAllowFds(false);
}
return ActivityManagerNative.getDefault().startInstrumentation(
- className, profileFile, 0, arguments, null);
+ className, profileFile, 0, arguments, null, getUserId());
} catch (RemoteException e) {
// System has crashed, nothing we can do.
}
@@ -1312,7 +1521,7 @@ class ContextImpl extends Context {
(message != null ? (message + ": ") : "") +
(selfToo
? "Neither user " + uid + " nor current process has "
- : "User " + uid + " does not have ") +
+ : "uid " + uid + " does not have ") +
permission +
".");
}
@@ -1484,7 +1693,13 @@ class ContextImpl extends Context {
@Override
public Context createPackageContext(String packageName, int flags)
- throws PackageManager.NameNotFoundException {
+ throws NameNotFoundException {
+ return createPackageContextAsUser(packageName, flags, Process.myUserHandle());
+ }
+
+ @Override
+ public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
+ throws NameNotFoundException {
if (packageName.equals("system") || packageName.equals("android")) {
final ContextImpl context = new ContextImpl(mMainThread.getSystemContext());
context.mBasePackageName = mBasePackageName;
@@ -1492,11 +1707,12 @@ class ContextImpl extends Context {
}
LoadedApk pi =
- mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags);
+ mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags,
+ user.getIdentifier());
if (pi != null) {
ContextImpl c = new ContextImpl();
c.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
- c.init(pi, null, mMainThread, mResources, mBasePackageName);
+ c.init(pi, null, mMainThread, mResources, mBasePackageName, user);
if (c.mResources != null) {
return c;
}
@@ -1508,10 +1724,55 @@ class ContextImpl extends Context {
}
@Override
+ public Context createConfigurationContext(Configuration overrideConfiguration) {
+ if (overrideConfiguration == null) {
+ throw new IllegalArgumentException("overrideConfiguration must not be null");
+ }
+
+ ContextImpl c = new ContextImpl();
+ c.init(mPackageInfo, null, mMainThread);
+ c.mResources = mMainThread.getTopLevelResources(
+ mPackageInfo.getResDir(),
+ getDisplayId(), overrideConfiguration,
+ mResources.getCompatibilityInfo());
+ return c;
+ }
+
+ @Override
+ public Context createDisplayContext(Display display) {
+ if (display == null) {
+ throw new IllegalArgumentException("display must not be null");
+ }
+
+ 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);
+ return context;
+ }
+
+ private int getDisplayId() {
+ return mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
+ }
+
+ @Override
public boolean isRestricted() {
return mRestricted;
}
+ @Override
+ public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
+ return displayId == Display.DEFAULT_DISPLAY ? mPackageInfo.mCompatibilityInfo : null;
+ }
+
private File getDataDirFile() {
if (mPackageInfo != null) {
return mPackageInfo.getDataDirFile();
@@ -1531,9 +1792,14 @@ class ContextImpl extends Context {
return file;
}
+ /** {@hide} */
+ public int getUserId() {
+ return mUser.getIdentifier();
+ }
+
static ContextImpl createSystemContext(ActivityThread mainThread) {
- ContextImpl context = new ContextImpl();
- context.init(Resources.getSystem(), mainThread);
+ final ContextImpl context = new ContextImpl();
+ context.init(Resources.getSystem(), mainThread, Process.myUserHandle());
return context;
}
@@ -1553,17 +1819,17 @@ class ContextImpl extends Context {
mResources = context.mResources;
mMainThread = context.mMainThread;
mContentResolver = context.mContentResolver;
+ mUser = context.mUser;
+ mDisplay = context.mDisplay;
mOuterContext = this;
}
- final void init(LoadedApk packageInfo,
- IBinder activityToken, ActivityThread mainThread) {
- init(packageInfo, activityToken, mainThread, null, null);
+ final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread) {
+ init(packageInfo, activityToken, mainThread, null, null, Process.myUserHandle());
}
- final void init(LoadedApk packageInfo,
- IBinder activityToken, ActivityThread mainThread,
- Resources container, String basePackageName) {
+ final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread,
+ Resources container, String basePackageName, UserHandle user) {
mPackageInfo = packageInfo;
mBasePackageName = basePackageName != null ? basePackageName : packageInfo.mPackageName;
mResources = mPackageInfo.getResources(mainThread);
@@ -1576,20 +1842,22 @@ class ContextImpl extends Context {
" compatiblity info:" + container.getDisplayMetrics());
}
mResources = mainThread.getTopLevelResources(
- mPackageInfo.getResDir(), container.getCompatibilityInfo());
+ mPackageInfo.getResDir(), Display.DEFAULT_DISPLAY,
+ null, container.getCompatibilityInfo());
}
mMainThread = mainThread;
- mContentResolver = new ApplicationContentResolver(this, mainThread);
-
- setActivityToken(activityToken);
+ mActivityToken = activityToken;
+ mContentResolver = new ApplicationContentResolver(this, mainThread, user);
+ mUser = user;
}
- final void init(Resources resources, ActivityThread mainThread) {
+ final void init(Resources resources, ActivityThread mainThread, UserHandle user) {
mPackageInfo = null;
mBasePackageName = null;
mResources = resources;
mMainThread = mainThread;
- mContentResolver = new ApplicationContentResolver(this, mainThread);
+ mContentResolver = new ApplicationContentResolver(this, mainThread, user);
+ mUser = user;
}
final void scheduleFinalCleanup(String who, String what) {
@@ -1608,10 +1876,6 @@ class ContextImpl extends Context {
return mReceiverRestrictedContext = new ReceiverRestrictedContext(getOuterContext());
}
- final void setActivityToken(IBinder token) {
- mActivityToken = token;
- }
-
final void setOuterContext(Context context) {
mOuterContext = context;
}
@@ -1678,19 +1942,24 @@ class ContextImpl extends Context {
// ----------------------------------------------------------------------
private static final class ApplicationContentResolver extends ContentResolver {
- public ApplicationContentResolver(Context context, ActivityThread mainThread) {
+ private final ActivityThread mMainThread;
+ private final UserHandle mUser;
+
+ public ApplicationContentResolver(
+ Context context, ActivityThread mainThread, UserHandle user) {
super(context);
- mMainThread = mainThread;
+ mMainThread = Preconditions.checkNotNull(mainThread);
+ mUser = Preconditions.checkNotNull(user);
}
@Override
- protected IContentProvider acquireProvider(Context context, String name) {
- return mMainThread.acquireProvider(context, name, true);
+ protected IContentProvider acquireProvider(Context context, String auth) {
+ return mMainThread.acquireProvider(context, auth, mUser.getIdentifier(), true);
}
@Override
- protected IContentProvider acquireExistingProvider(Context context, String name) {
- return mMainThread.acquireExistingProvider(context, name, true);
+ protected IContentProvider acquireExistingProvider(Context context, String auth) {
+ return mMainThread.acquireExistingProvider(context, auth, mUser.getIdentifier(), true);
}
@Override
@@ -1699,8 +1968,8 @@ class ContextImpl extends Context {
}
@Override
- protected IContentProvider acquireUnstableProvider(Context c, String name) {
- return mMainThread.acquireProvider(c, name, false);
+ protected IContentProvider acquireUnstableProvider(Context c, String auth) {
+ return mMainThread.acquireProvider(c, auth, mUser.getIdentifier(), false);
}
@Override
@@ -1712,7 +1981,5 @@ class ContextImpl extends Context {
public void unstableProviderDied(IContentProvider icp) {
mMainThread.handleUnstableProviderDied(icp.asBinder(), true);
}
-
- private final ActivityThread mMainThread;
}
}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 2cc3b02..b3d99c5 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -147,15 +147,19 @@ public class Dialog implements DialogInterface, Window.Callback,
this(context, theme, true);
}
- Dialog(Context context, int theme, boolean createContextWrapper) {
- if (theme == 0) {
- TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
- outValue, true);
- theme = outValue.resourceId;
+ Dialog(Context context, int theme, boolean createContextThemeWrapper) {
+ if (createContextThemeWrapper) {
+ if (theme == 0) {
+ TypedValue outValue = new TypedValue();
+ context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
+ outValue, true);
+ theme = outValue.resourceId;
+ }
+ mContext = new ContextThemeWrapper(context, theme);
+ } else {
+ mContext = context;
}
- mContext = createContextWrapper ? new ContextThemeWrapper(context, theme) : context;
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w;
@@ -164,7 +168,7 @@ public class Dialog implements DialogInterface, Window.Callback,
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
-
+
/**
* @deprecated
* @hide
@@ -1106,10 +1110,12 @@ public class Dialog implements DialogInterface, Window.Callback,
/**
* Set a listener to be invoked when the dialog is canceled.
- * <p>
- * This will only be invoked when the dialog is canceled, if the creator
- * needs to know when it is dismissed in general, use
- * {@link #setOnDismissListener}.
+ *
+ * <p>This will only be invoked when the dialog is canceled.
+ * Cancel events alone will not capture all ways that
+ * the dialog might be dismissed. If the creator needs
+ * to know when a dialog is dismissed in general, use
+ * {@link #setOnDismissListener}.</p>
*
* @param listener The {@link DialogInterface.OnCancelListener} to use.
*/
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 0b1c524..6cf4dd0 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1098,8 +1098,8 @@ public class DownloadManager {
*/
public static Long getMaxBytesOverMobile(Context context) {
try {
- return Settings.Secure.getLong(context.getContentResolver(),
- Settings.Secure.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
+ return Settings.Global.getLong(context.getContentResolver(),
+ Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
} catch (SettingNotFoundException exc) {
return null;
}
@@ -1116,8 +1116,8 @@ public class DownloadManager {
*/
public static Long getRecommendedMaxBytesOverMobile(Context context) {
try {
- return Settings.Secure.getLong(context.getContentResolver(),
- Settings.Secure.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
+ return Settings.Global.getLong(context.getContentResolver(),
+ Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
} catch (SettingNotFoundException exc) {
return null;
}
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 28876d3..c5a382d 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -85,7 +85,7 @@ final class FragmentState implements Parcelable {
mSavedFragmentState = in.readBundle();
}
- public Fragment instantiate(Activity activity) {
+ public Fragment instantiate(Activity activity, Fragment parent) {
if (mInstance != null) {
return mInstance;
}
@@ -100,7 +100,7 @@ final class FragmentState implements Parcelable {
mSavedFragmentState.setClassLoader(activity.getClassLoader());
mInstance.mSavedFragmentState = mSavedFragmentState;
}
- mInstance.setIndex(mIndex);
+ mInstance.setIndex(mIndex, parent);
mInstance.mFromLayout = mFromLayout;
mInstance.mRestored = true;
mInstance.mFragmentId = mFragmentId;
@@ -207,6 +207,8 @@ final class FragmentState implements Parcelable {
* with the fragment.
* <li> {@link #onActivityCreated} tells the fragment that its activity has
* completed its own {@link Activity#onCreate Activity.onCreate()}.
+ * <li> {@link #onViewStateRestored} tells the fragment that all of the saved
+ * state of its view hierarchy has been restored.
* <li> {@link #onStart} makes the fragment visible to the user (based on its
* containing activity being started).
* <li> {@link #onResume} makes the fragment interacting with the user (based on its
@@ -412,7 +414,13 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
// Activity this fragment is attached to.
Activity mActivity;
-
+
+ // Private fragment manager for child fragments inside of this one.
+ FragmentManagerImpl mChildFragmentManager;
+
+ // If this Fragment is contained in another Fragment, this is that container.
+ Fragment mParentFragment;
+
// The optional identifier for this fragment -- either the container ID if it
// was dynamically added to the view hierarchy, or the ID supplied in
// layout.
@@ -595,18 +603,28 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
}
}
- final void restoreViewState() {
+ final void restoreViewState(Bundle savedInstanceState) {
if (mSavedViewState != null) {
mView.restoreHierarchyState(mSavedViewState);
mSavedViewState = null;
}
+ mCalled = false;
+ onViewStateRestored(savedInstanceState);
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onViewStateRestored()");
+ }
}
-
- final void setIndex(int index) {
+
+ final void setIndex(int index, Fragment parent) {
mIndex = index;
- mWho = "android:fragment:" + mIndex;
- }
-
+ if (parent != null) {
+ mWho = parent.mWho + ":" + mIndex;
+ } else {
+ mWho = "android:fragment:" + mIndex;
+ }
+ }
+
final boolean isInBackStack() {
return mBackStackNesting > 0;
}
@@ -785,12 +803,43 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
* before {@link #getActivity()}, during the time from when the fragment is
* placed in a {@link FragmentTransaction} until it is committed and
* attached to its activity.
+ *
+ * <p>If this Fragment is a child of another Fragment, the FragmentManager
+ * returned here will be the parent's {@link #getChildFragmentManager()}.
*/
final public FragmentManager getFragmentManager() {
return mFragmentManager;
}
/**
+ * Return a private FragmentManager for placing and managing Fragments
+ * inside of this Fragment.
+ */
+ final public FragmentManager getChildFragmentManager() {
+ if (mChildFragmentManager == null) {
+ instantiateChildFragmentManager();
+ if (mState >= RESUMED) {
+ mChildFragmentManager.dispatchResume();
+ } else if (mState >= STARTED) {
+ mChildFragmentManager.dispatchStart();
+ } else if (mState >= ACTIVITY_CREATED) {
+ mChildFragmentManager.dispatchActivityCreated();
+ } else if (mState >= CREATED) {
+ mChildFragmentManager.dispatchCreate();
+ }
+ }
+ return mChildFragmentManager;
+ }
+
+ /**
+ * Returns the parent Fragment containing this Fragment. If this Fragment
+ * is attached directly to an Activity, returns null.
+ */
+ final public Fragment getParentFragment() {
+ return mParentFragment;
+ }
+
+ /**
* Return true if the fragment is currently added to its activity.
*/
final public boolean isAdded() {
@@ -880,6 +929,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
* </ul>
*/
public void setRetainInstance(boolean retain) {
+ if (retain && mParentFragment != null) {
+ throw new IllegalStateException(
+ "Can't retain fragements that are nested in other fragments");
+ }
mRetainInstance = retain;
}
@@ -961,7 +1014,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
mCheckedForLoaderManager = true;
- mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, true);
+ mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, true);
return mLoaderManager;
}
@@ -1135,20 +1188,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
public void onCreate(Bundle savedInstanceState) {
mCalled = true;
}
-
- /**
- * Called immediately after {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}
- * has returned, but before any saved state has been restored in to the view.
- * This gives subclasses a chance to initialize themselves once
- * they know their view hierarchy has been completely created. The fragment's
- * view hierarchy is not however attached to its parent at this point.
- * @param view The View returned by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
- * @param savedInstanceState If non-null, this fragment is being re-constructed
- * from a previous saved state as given here.
- */
- public void onViewCreated(View view, Bundle savedInstanceState) {
- }
-
+
/**
* Called to have the fragment instantiate its user interface view.
* This is optional, and non-graphical fragments can return null (which
@@ -1172,6 +1212,19 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
Bundle savedInstanceState) {
return null;
}
+
+ /**
+ * Called immediately after {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}
+ * has returned, but before any saved state has been restored in to the view.
+ * This gives subclasses a chance to initialize themselves once
+ * they know their view hierarchy has been completely created. The fragment's
+ * view hierarchy is not however attached to its parent at this point.
+ * @param view The View returned by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+ * @param savedInstanceState If non-null, this fragment is being re-constructed
+ * from a previous saved state as given here.
+ */
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ }
/**
* Get the root view for the fragment's layout (the one returned by {@link #onCreateView}),
@@ -1191,15 +1244,30 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
* {@link #setRetainInstance(boolean)} to retain their instance,
* as this callback tells the fragment when it is fully associated with
* the new activity instance. This is called after {@link #onCreateView}
- * and before {@link #onStart()}.
- *
+ * and before {@link #onViewStateRestored(Bundle)}.
+ *
* @param savedInstanceState If the fragment is being re-created from
* a previous saved state, this is the state.
*/
public void onActivityCreated(Bundle savedInstanceState) {
mCalled = true;
}
-
+
+ /**
+ * Called when all saved state has been restored into the view hierarchy
+ * of the fragment. This can be used to do initialization based on saved
+ * state that you are letting the view hierarchy track itself, such as
+ * whether check box widgets are currently checked. This is called
+ * after {@link #onActivityCreated(Bundle)} and before
+ * {@link #onStart()}.
+ *
+ * @param savedInstanceState If the fragment is being re-created from
+ * a previous saved state, this is the state.
+ */
+ public void onViewStateRestored(Bundle savedInstanceState) {
+ mCalled = true;
+ }
+
/**
* Called when the Fragment is visible to the user. This is generally
* tied to {@link Activity#onStart() Activity.onStart} of the containing
@@ -1212,7 +1280,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
mLoadersStarted = true;
if (!mCheckedForLoaderManager) {
mCheckedForLoaderManager = true;
- mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, false);
+ mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);
}
if (mLoaderManager != null) {
mLoaderManager.doStart();
@@ -1305,7 +1373,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
// + " mLoaderManager=" + mLoaderManager);
if (!mCheckedForLoaderManager) {
mCheckedForLoaderManager = true;
- mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, false);
+ mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);
}
if (mLoaderManager != null) {
mLoaderManager.doDestroy();
@@ -1530,6 +1598,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
writer.print(prefix); writer.print("mActivity=");
writer.println(mActivity);
}
+ if (mParentFragment != null) {
+ writer.print(prefix); writer.print("mParentFragment=");
+ writer.println(mParentFragment);
+ }
if (mArguments != null) {
writer.print(prefix); writer.print("mArguments="); writer.println(mArguments);
}
@@ -1564,23 +1636,244 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
writer.print(prefix); writer.println("Loader Manager:");
mLoaderManager.dump(prefix + " ", fd, writer, args);
}
+ if (mChildFragmentManager != null) {
+ writer.print(prefix); writer.println("Child " + mChildFragmentManager + ":");
+ mChildFragmentManager.dump(prefix + " ", fd, writer, args);
+ }
+ }
+
+ Fragment findFragmentByWho(String who) {
+ if (who.equals(mWho)) {
+ return this;
+ }
+ if (mChildFragmentManager != null) {
+ return mChildFragmentManager.findFragmentByWho(who);
+ }
+ return null;
+ }
+
+ void instantiateChildFragmentManager() {
+ mChildFragmentManager = new FragmentManagerImpl();
+ mChildFragmentManager.attachActivity(mActivity, new FragmentContainer() {
+ @Override
+ public View findViewById(int id) {
+ if (mView == null) {
+ throw new IllegalStateException("Fragment does not have a view");
+ }
+ return mView.findViewById(id);
+ }
+ }, this);
+ }
+
+ void performCreate(Bundle savedInstanceState) {
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.noteStateNotSaved();
+ }
+ mCalled = false;
+ onCreate(savedInstanceState);
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onCreate()");
+ }
+ if (savedInstanceState != null) {
+ Parcelable p = savedInstanceState.getParcelable(Activity.FRAGMENTS_TAG);
+ if (p != null) {
+ if (mChildFragmentManager == null) {
+ instantiateChildFragmentManager();
+ }
+ mChildFragmentManager.restoreAllState(p, null);
+ mChildFragmentManager.dispatchCreate();
+ }
+ }
+ }
+
+ View performCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.noteStateNotSaved();
+ }
+ return onCreateView(inflater, container, savedInstanceState);
+ }
+
+ void performActivityCreated(Bundle savedInstanceState) {
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.noteStateNotSaved();
+ }
+ mCalled = false;
+ onActivityCreated(savedInstanceState);
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onActivityCreated()");
+ }
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchActivityCreated();
+ }
}
void performStart() {
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.noteStateNotSaved();
+ mChildFragmentManager.execPendingActions();
+ }
+ mCalled = false;
onStart();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onStart()");
+ }
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchStart();
+ }
if (mLoaderManager != null) {
mLoaderManager.doReportStart();
}
}
+ void performResume() {
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.noteStateNotSaved();
+ mChildFragmentManager.execPendingActions();
+ }
+ mCalled = false;
+ onResume();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onResume()");
+ }
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchResume();
+ mChildFragmentManager.execPendingActions();
+ }
+ }
+
+ void performConfigurationChanged(Configuration newConfig) {
+ onConfigurationChanged(newConfig);
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchConfigurationChanged(newConfig);
+ }
+ }
+
+ void performLowMemory() {
+ onLowMemory();
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchLowMemory();
+ }
+ }
+
+ void performTrimMemory(int level) {
+ onTrimMemory(level);
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchTrimMemory(level);
+ }
+ }
+
+ boolean performCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ boolean show = false;
+ if (!mHidden) {
+ if (mHasMenu && mMenuVisible) {
+ show = true;
+ onCreateOptionsMenu(menu, inflater);
+ }
+ if (mChildFragmentManager != null) {
+ show |= mChildFragmentManager.dispatchCreateOptionsMenu(menu, inflater);
+ }
+ }
+ return show;
+ }
+
+ boolean performPrepareOptionsMenu(Menu menu) {
+ boolean show = false;
+ if (!mHidden) {
+ if (mHasMenu && mMenuVisible) {
+ show = true;
+ onPrepareOptionsMenu(menu);
+ }
+ if (mChildFragmentManager != null) {
+ show |= mChildFragmentManager.dispatchPrepareOptionsMenu(menu);
+ }
+ }
+ return show;
+ }
+
+ boolean performOptionsItemSelected(MenuItem item) {
+ if (!mHidden) {
+ if (mHasMenu && mMenuVisible) {
+ if (onOptionsItemSelected(item)) {
+ return true;
+ }
+ }
+ if (mChildFragmentManager != null) {
+ if (mChildFragmentManager.dispatchOptionsItemSelected(item)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ boolean performContextItemSelected(MenuItem item) {
+ if (!mHidden) {
+ if (onContextItemSelected(item)) {
+ return true;
+ }
+ if (mChildFragmentManager != null) {
+ if (mChildFragmentManager.dispatchContextItemSelected(item)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ void performOptionsMenuClosed(Menu menu) {
+ if (!mHidden) {
+ if (mHasMenu && mMenuVisible) {
+ onOptionsMenuClosed(menu);
+ }
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchOptionsMenuClosed(menu);
+ }
+ }
+ }
+
+ void performSaveInstanceState(Bundle outState) {
+ onSaveInstanceState(outState);
+ if (mChildFragmentManager != null) {
+ Parcelable p = mChildFragmentManager.saveAllState();
+ if (p != null) {
+ outState.putParcelable(Activity.FRAGMENTS_TAG, p);
+ }
+ }
+ }
+
+ void performPause() {
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchPause();
+ }
+ mCalled = false;
+ onPause();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onPause()");
+ }
+ }
+
void performStop() {
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchStop();
+ }
+ mCalled = false;
onStop();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onStop()");
+ }
if (mLoadersStarted) {
mLoadersStarted = false;
if (!mCheckedForLoaderManager) {
mCheckedForLoaderManager = true;
- mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, false);
+ mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false);
}
if (mLoaderManager != null) {
if (mActivity == null || !mActivity.mChangingConfigurations) {
@@ -1593,9 +1886,29 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
}
void performDestroyView() {
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchDestroyView();
+ }
+ mCalled = false;
onDestroyView();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onDestroyView()");
+ }
if (mLoaderManager != null) {
mLoaderManager.doReportNextStart();
}
}
+
+ void performDestroy() {
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchDestroy();
+ }
+ mCalled = false;
+ onDestroy();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onDestroy()");
+ }
+ }
}
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 39e2423..e983299 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -22,6 +22,7 @@ import android.animation.AnimatorListenerAdapter;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcel;
@@ -29,7 +30,6 @@ import android.os.Parcelable;
import android.util.DebugUtils;
import android.util.Log;
import android.util.LogWriter;
-import android.util.Slog;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuInflater;
@@ -380,6 +380,13 @@ final class FragmentManagerState implements Parcelable {
}
/**
+ * Callbacks from FragmentManagerImpl to its container.
+ */
+interface FragmentContainer {
+ public View findViewById(int id);
+}
+
+/**
* Container for fragments associated with an activity.
*/
final class FragmentManagerImpl extends FragmentManager {
@@ -409,6 +416,8 @@ final class FragmentManagerImpl extends FragmentManager {
int mCurState = Fragment.INITIALIZING;
Activity mActivity;
+ FragmentContainer mContainer;
+ Fragment mParent;
boolean mNeedMenuInvalidate;
boolean mStateSaved;
@@ -427,6 +436,28 @@ 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);
+ if (mActivity != null) {
+ Log.e(TAG, "Activity state:");
+ try {
+ mActivity.dump(" ", null, pw, new String[] { });
+ } catch (Exception e) {
+ Log.e(TAG, "Failed dumping state", e);
+ }
+ } else {
+ Log.e(TAG, "Fragment manager state:");
+ try {
+ dump(" ", null, pw, new String[] { });
+ } catch (Exception e) {
+ Log.e(TAG, "Failed dumping state", e);
+ }
+ }
+ throw ex;
+ }
+
@Override
public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
@@ -519,8 +550,8 @@ final class FragmentManagerImpl extends FragmentManager {
@Override
public void putFragment(Bundle bundle, String key, Fragment fragment) {
if (fragment.mIndex < 0) {
- throw new IllegalStateException("Fragment " + fragment
- + " is not currently in the FragmentManager");
+ throwException(new IllegalStateException("Fragment " + fragment
+ + " is not currently in the FragmentManager"));
}
bundle.putInt(key, fragment.mIndex);
}
@@ -532,13 +563,13 @@ final class FragmentManagerImpl extends FragmentManager {
return null;
}
if (index >= mActive.size()) {
- throw new IllegalStateException("Fragement no longer exists for key "
- + key + ": index " + index);
+ throwException(new IllegalStateException("Fragement no longer exists for key "
+ + key + ": index " + index));
}
Fragment f = mActive.get(index);
if (f == null) {
- throw new IllegalStateException("Fragement no longer exists for key "
- + key + ": index " + index);
+ throwException(new IllegalStateException("Fragement no longer exists for key "
+ + key + ": index " + index));
}
return f;
}
@@ -546,8 +577,8 @@ final class FragmentManagerImpl extends FragmentManager {
@Override
public Fragment.SavedState saveFragmentInstanceState(Fragment fragment) {
if (fragment.mIndex < 0) {
- throw new IllegalStateException("Fragment " + fragment
- + " is not currently in the FragmentManager");
+ throwException(new IllegalStateException("Fragment " + fragment
+ + " is not currently in the FragmentManager"));
}
if (fragment.mState > Fragment.INITIALIZING) {
Bundle result = saveFragmentBasicState(fragment);
@@ -562,7 +593,11 @@ final class FragmentManagerImpl extends FragmentManager {
sb.append("FragmentManager{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" in ");
- DebugUtils.buildShortClassTag(mActivity, sb);
+ if (mParent != null) {
+ DebugUtils.buildShortClassTag(mParent, sb);
+ } else {
+ DebugUtils.buildShortClassTag(mActivity, sb);
+ }
sb.append("}}");
return sb.toString();
}
@@ -658,6 +693,11 @@ final class FragmentManagerImpl extends FragmentManager {
}
writer.print(prefix); writer.println("FragmentManager misc state:");
+ writer.print(prefix); writer.print(" mActivity="); writer.println(mActivity);
+ writer.print(prefix); writer.print(" mContainer="); writer.println(mContainer);
+ if (mParent != null) {
+ writer.print(prefix); writer.print(" mParent="); writer.println(mParent);
+ }
writer.print(prefix); writer.print(" mCurState="); writer.print(mCurState);
writer.print(" mStateSaved="); writer.print(mStateSaved);
writer.print(" mDestroyed="); writer.println(mDestroyed);
@@ -732,8 +772,12 @@ final class FragmentManagerImpl extends FragmentManager {
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
+ if (DEBUG && false) Log.v(TAG, "moveToState: " + f
+ + " oldState=" + f.mState + " newState=" + newState
+ + " mRemoving=" + f.mRemoving + " Callers=" + Debug.getCallers(5));
+
// Fragments that are not currently added will sit in the onCreate() state.
- if (!f.mAdded && newState > Fragment.CREATED) {
+ if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
newState = Fragment.CREATED;
}
if (f.mRemoving && newState > f.mState) {
@@ -782,30 +826,29 @@ final class FragmentManagerImpl extends FragmentManager {
}
}
f.mActivity = mActivity;
- f.mFragmentManager = mActivity.mFragments;
+ f.mParentFragment = mParent;
+ f.mFragmentManager = mParent != null
+ ? mParent.mChildFragmentManager : mActivity.mFragments;
f.mCalled = false;
f.onAttach(mActivity);
if (!f.mCalled) {
throw new SuperNotCalledException("Fragment " + f
+ " did not call through to super.onAttach()");
}
- mActivity.onAttachFragment(f);
-
+ if (f.mParentFragment == null) {
+ mActivity.onAttachFragment(f);
+ }
+
if (!f.mRetaining) {
- f.mCalled = false;
- f.onCreate(f.mSavedFragmentState);
- if (!f.mCalled) {
- throw new SuperNotCalledException("Fragment " + f
- + " did not call through to super.onCreate()");
- }
+ f.performCreate(f.mSavedFragmentState);
}
f.mRetaining = false;
if (f.mFromLayout) {
// For fragments that are part of the content view
// layout, we need to instantiate the view immediately
// and the inflater will take care of adding it.
- f.mView = f.onCreateView(f.getLayoutInflater(f.mSavedFragmentState),
- null, f.mSavedFragmentState);
+ f.mView = f.performCreateView(f.getLayoutInflater(
+ f.mSavedFragmentState), null, f.mSavedFragmentState);
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
if (f.mHidden) f.mView.setVisibility(View.GONE);
@@ -818,16 +861,18 @@ final class FragmentManagerImpl extends FragmentManager {
if (!f.mFromLayout) {
ViewGroup container = null;
if (f.mContainerId != 0) {
- container = (ViewGroup)mActivity.findViewById(f.mContainerId);
+ container = (ViewGroup)mContainer.findViewById(f.mContainerId);
if (container == null && !f.mRestored) {
- throw new IllegalArgumentException("No view found for id 0x"
- + Integer.toHexString(f.mContainerId)
- + " for fragment " + f);
+ throwException(new IllegalArgumentException(
+ "No view found for id 0x"
+ + Integer.toHexString(f.mContainerId) + " ("
+ + f.getResources().getResourceName(f.mContainerId)
+ + ") for fragment " + f));
}
}
f.mContainer = container;
- f.mView = f.onCreateView(f.getLayoutInflater(f.mSavedFragmentState),
- container, f.mSavedFragmentState);
+ f.mView = f.performCreateView(f.getLayoutInflater(
+ f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
if (container != null) {
@@ -843,15 +888,10 @@ final class FragmentManagerImpl extends FragmentManager {
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
}
-
- f.mCalled = false;
- f.onActivityCreated(f.mSavedFragmentState);
- if (!f.mCalled) {
- throw new SuperNotCalledException("Fragment " + f
- + " did not call through to super.onActivityCreated()");
- }
+
+ f.performActivityCreated(f.mSavedFragmentState);
if (f.mView != null) {
- f.restoreViewState();
+ f.restoreViewState(f.mSavedFragmentState);
}
f.mSavedFragmentState = null;
}
@@ -859,23 +899,13 @@ final class FragmentManagerImpl extends FragmentManager {
case Fragment.STOPPED:
if (newState > Fragment.STOPPED) {
if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
- f.mCalled = false;
f.performStart();
- if (!f.mCalled) {
- throw new SuperNotCalledException("Fragment " + f
- + " did not call through to super.onStart()");
- }
}
case Fragment.STARTED:
if (newState > Fragment.STARTED) {
if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
- f.mCalled = false;
f.mResumed = true;
- f.onResume();
- if (!f.mCalled) {
- throw new SuperNotCalledException("Fragment " + f
- + " did not call through to super.onResume()");
- }
+ f.performResume();
// Get rid of this in case we saved it and never needed it.
f.mSavedFragmentState = null;
f.mSavedViewState = null;
@@ -886,23 +916,13 @@ final class FragmentManagerImpl extends FragmentManager {
case Fragment.RESUMED:
if (newState < Fragment.RESUMED) {
if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f);
- f.mCalled = false;
- f.onPause();
- if (!f.mCalled) {
- throw new SuperNotCalledException("Fragment " + f
- + " did not call through to super.onPause()");
- }
+ f.performPause();
f.mResumed = false;
}
case Fragment.STARTED:
if (newState < Fragment.STARTED) {
if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f);
- f.mCalled = false;
f.performStop();
- if (!f.mCalled) {
- throw new SuperNotCalledException("Fragment " + f
- + " did not call through to super.onStop()");
- }
}
case Fragment.STOPPED:
case Fragment.ACTIVITY_CREATED:
@@ -915,12 +935,7 @@ final class FragmentManagerImpl extends FragmentManager {
saveFragmentViewState(f);
}
}
- f.mCalled = false;
f.performDestroyView();
- if (!f.mCalled) {
- throw new SuperNotCalledException("Fragment " + f
- + " did not call through to super.onDestroyView()");
- }
if (f.mView != null && f.mContainer != null) {
Animator anim = null;
if (mCurState > Fragment.INITIALIZING && !mDestroyed) {
@@ -979,12 +994,7 @@ final class FragmentManagerImpl extends FragmentManager {
} else {
if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
if (!f.mRetaining) {
- f.mCalled = false;
- f.onDestroy();
- if (!f.mCalled) {
- throw new SuperNotCalledException("Fragment " + f
- + " did not call through to super.onDestroy()");
- }
+ f.performDestroy();
}
f.mCalled = false;
@@ -998,6 +1008,7 @@ final class FragmentManagerImpl extends FragmentManager {
makeInactive(f);
} else {
f.mActivity = null;
+ f.mParentFragment = null;
f.mFragmentManager = null;
}
}
@@ -1021,11 +1032,11 @@ final class FragmentManagerImpl extends FragmentManager {
if (mActivity == null && newState != Fragment.INITIALIZING) {
throw new IllegalStateException("No activity");
}
-
+
if (!always && mCurState == newState) {
return;
}
-
+
mCurState = newState;
if (mActive != null) {
boolean loadersRunning = false;
@@ -1070,11 +1081,11 @@ final class FragmentManagerImpl extends FragmentManager {
if (mActive == null) {
mActive = new ArrayList<Fragment>();
}
- f.setIndex(mActive.size());
+ f.setIndex(mActive.size(), mParent);
mActive.add(f);
} else {
- f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1));
+ f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent);
mActive.set(f.mIndex, f);
}
if (DEBUG) Log.v(TAG, "Allocated fragment index " + f);
@@ -1091,7 +1102,7 @@ final class FragmentManagerImpl extends FragmentManager {
mAvailIndices = new ArrayList<Integer>();
}
mAvailIndices.add(f.mIndex);
- mActivity.invalidateFragmentIndex(f.mIndex);
+ mActivity.invalidateFragment(f.mWho);
f.initState();
}
@@ -1102,6 +1113,9 @@ final class FragmentManagerImpl extends FragmentManager {
if (DEBUG) Log.v(TAG, "add: " + fragment);
makeActive(fragment);
if (!fragment.mDetached) {
+ if (mAdded.contains(fragment)) {
+ throw new IllegalStateException("Fragment already added: " + fragment);
+ }
mAdded.add(fragment);
fragment.mAdded = true;
fragment.mRemoving = false;
@@ -1118,6 +1132,14 @@ final class FragmentManagerImpl extends FragmentManager {
if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
final boolean inactive = !fragment.isInBackStack();
if (!fragment.mDetached || inactive) {
+ if (false) {
+ // Would be nice to catch a bad remove here, but we need
+ // time to test this to make sure we aren't crashes cases
+ // where it is not a problem.
+ if (!mAdded.contains(fragment)) {
+ throw new IllegalStateException("Fragment not added: " + fragment);
+ }
+ }
if (mAdded != null) {
mAdded.remove(fragment);
}
@@ -1190,6 +1212,7 @@ final class FragmentManagerImpl extends FragmentManager {
if (fragment.mAdded) {
// We are not already in back stack, so need to remove the fragment.
if (mAdded != null) {
+ if (DEBUG) Log.v(TAG, "remove from detach: " + fragment);
mAdded.remove(fragment);
}
if (fragment.mHasMenu && fragment.mMenuVisible) {
@@ -1209,6 +1232,10 @@ final class FragmentManagerImpl extends FragmentManager {
if (mAdded == null) {
mAdded = new ArrayList<Fragment>();
}
+ if (mAdded.contains(fragment)) {
+ throw new IllegalStateException("Fragment already added: " + fragment);
+ }
+ if (DEBUG) Log.v(TAG, "add from attach: " + fragment);
mAdded.add(fragment);
fragment.mAdded = true;
if (fragment.mHasMenu && fragment.mMenuVisible) {
@@ -1267,7 +1294,7 @@ final class FragmentManagerImpl extends FragmentManager {
if (mActive != null && who != null) {
for (int i=mActive.size()-1; i>=0; i--) {
Fragment f = mActive.get(i);
- if (f != null && who.equals(f.mWho)) {
+ if (f != null && (f=f.findFragmentByWho(who)) != null) {
return f;
}
}
@@ -1537,7 +1564,7 @@ final class FragmentManagerImpl extends FragmentManager {
if (mStateBundle == null) {
mStateBundle = new Bundle();
}
- f.onSaveInstanceState(mStateBundle);
+ f.performSaveInstanceState(mStateBundle);
if (!mStateBundle.isEmpty()) {
result = mStateBundle;
mStateBundle = null;
@@ -1583,12 +1610,9 @@ final class FragmentManagerImpl extends FragmentManager {
Fragment f = mActive.get(i);
if (f != null) {
if (f.mIndex < 0) {
- String msg = "Failure saving state: active " + f
- + " has cleared index: " + f.mIndex;
- Slog.e(TAG, msg);
- dump(" ", null, new PrintWriter(new LogWriter(
- Log.ERROR, TAG, Log.LOG_ID_SYSTEM)), new String[] { });
- throw new IllegalStateException(msg);
+ throwException(new IllegalStateException(
+ "Failure saving state: active " + f
+ + " has cleared index: " + f.mIndex));
}
haveFragments = true;
@@ -1601,12 +1625,9 @@ final class FragmentManagerImpl extends FragmentManager {
if (f.mTarget != null) {
if (f.mTarget.mIndex < 0) {
- String msg = "Failure saving state: " + f
- + " has target not in fragment manager: " + f.mTarget;
- Slog.e(TAG, msg);
- dump(" ", null, new PrintWriter(new LogWriter(
- Log.ERROR, TAG, Log.LOG_ID_SYSTEM)), new String[] { });
- throw new IllegalStateException(msg);
+ throwException(new IllegalStateException(
+ "Failure saving state: " + f
+ + " has target not in fragment manager: " + f.mTarget));
}
if (fs.mSavedFragmentState == null) {
fs.mSavedFragmentState = new Bundle();
@@ -1645,12 +1666,9 @@ final class FragmentManagerImpl extends FragmentManager {
for (int i=0; i<N; i++) {
added[i] = mAdded.get(i).mIndex;
if (added[i] < 0) {
- String msg = "Failure saving state: active " + mAdded.get(i)
- + " has cleared index: " + added[i];
- Slog.e(TAG, msg);
- dump(" ", null, new PrintWriter(new LogWriter(
- Log.ERROR, TAG, Log.LOG_ID_SYSTEM)), new String[] { });
- throw new IllegalStateException(msg);
+ throwException(new IllegalStateException(
+ "Failure saving state: active " + mAdded.get(i)
+ + " has cleared index: " + added[i]));
}
if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i
+ ": " + mAdded.get(i));
@@ -1715,20 +1733,19 @@ final class FragmentManagerImpl extends FragmentManager {
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
- Fragment f = fs.instantiate(mActivity);
- if (DEBUG) Log.v(TAG, "restoreAllState: adding #" + i + ": " + f);
+ Fragment f = fs.instantiate(mActivity, mParent);
+ if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
mActive.add(f);
// Now that the fragment is instantiated (or came from being
// retained above), clear mInstance in case we end up re-restoring
// from this FragmentState again.
fs.mInstance = null;
} else {
- if (DEBUG) Log.v(TAG, "restoreAllState: adding #" + i + ": (null)");
mActive.add(null);
if (mAvailIndices == null) {
mAvailIndices = new ArrayList<Integer>();
}
- if (DEBUG) Log.v(TAG, "restoreAllState: adding avail #" + i);
+ if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
mAvailIndices.add(i);
}
}
@@ -1755,11 +1772,14 @@ final class FragmentManagerImpl extends FragmentManager {
for (int i=0; i<fms.mAdded.length; i++) {
Fragment f = mActive.get(fms.mAdded[i]);
if (f == null) {
- throw new IllegalStateException(
- "No instantiated fragment for index #" + fms.mAdded[i]);
+ throwException(new IllegalStateException(
+ "No instantiated fragment for index #" + fms.mAdded[i]));
}
f.mAdded = true;
- if (DEBUG) Log.v(TAG, "restoreAllState: making added #" + i + ": " + f);
+ if (DEBUG) Log.v(TAG, "restoreAllState: added #" + i + ": " + f);
+ if (mAdded.contains(f)) {
+ throw new IllegalStateException("Already added!");
+ }
mAdded.add(f);
}
} else {
@@ -1771,8 +1791,13 @@ final class FragmentManagerImpl extends FragmentManager {
mBackStack = new ArrayList<BackStackRecord>(fms.mBackStack.length);
for (int i=0; i<fms.mBackStack.length; i++) {
BackStackRecord bse = fms.mBackStack[i].instantiate(this);
- if (DEBUG) Log.v(TAG, "restoreAllState: adding bse #" + i
+ if (DEBUG) {
+ Log.v(TAG, "restoreAllState: back stack #" + i
+ " (index " + bse.mIndex + "): " + bse);
+ LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
+ PrintWriter pw = new PrintWriter(logw);
+ bse.dump(" ", pw, false);
+ }
mBackStack.add(bse);
if (bse.mIndex >= 0) {
setBackStackIndex(bse.mIndex, bse);
@@ -1783,9 +1808,11 @@ final class FragmentManagerImpl extends FragmentManager {
}
}
- public void attachActivity(Activity activity) {
- if (mActivity != null) throw new IllegalStateException();
+ public void attachActivity(Activity activity, FragmentContainer container, Fragment parent) {
+ if (mActivity != null) throw new IllegalStateException("Already attached");
mActivity = activity;
+ mContainer = container;
+ mParent = parent;
}
public void noteStateNotSaved() {
@@ -1820,11 +1847,17 @@ final class FragmentManagerImpl extends FragmentManager {
moveToState(Fragment.STOPPED, false);
}
+ public void dispatchDestroyView() {
+ moveToState(Fragment.CREATED, false);
+ }
+
public void dispatchDestroy() {
mDestroyed = true;
execPendingActions();
moveToState(Fragment.INITIALIZING, false);
mActivity = null;
+ mContainer = null;
+ mParent = null;
}
public void dispatchConfigurationChanged(Configuration newConfig) {
@@ -1832,7 +1865,7 @@ final class FragmentManagerImpl extends FragmentManager {
for (int i=0; i<mAdded.size(); i++) {
Fragment f = mAdded.get(i);
if (f != null) {
- f.onConfigurationChanged(newConfig);
+ f.performConfigurationChanged(newConfig);
}
}
}
@@ -1843,7 +1876,7 @@ final class FragmentManagerImpl extends FragmentManager {
for (int i=0; i<mAdded.size(); i++) {
Fragment f = mAdded.get(i);
if (f != null) {
- f.onLowMemory();
+ f.performLowMemory();
}
}
}
@@ -1854,7 +1887,7 @@ final class FragmentManagerImpl extends FragmentManager {
for (int i=0; i<mAdded.size(); i++) {
Fragment f = mAdded.get(i);
if (f != null) {
- f.onTrimMemory(level);
+ f.performTrimMemory(level);
}
}
}
@@ -1866,13 +1899,14 @@ final class FragmentManagerImpl extends FragmentManager {
if (mAdded != null) {
for (int i=0; i<mAdded.size(); i++) {
Fragment f = mAdded.get(i);
- if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) {
- show = true;
- f.onCreateOptionsMenu(menu, inflater);
- if (newMenus == null) {
- newMenus = new ArrayList<Fragment>();
+ if (f != null) {
+ if (f.performCreateOptionsMenu(menu, inflater)) {
+ show = true;
+ if (newMenus == null) {
+ newMenus = new ArrayList<Fragment>();
+ }
+ newMenus.add(f);
}
- newMenus.add(f);
}
}
}
@@ -1896,9 +1930,10 @@ final class FragmentManagerImpl extends FragmentManager {
if (mAdded != null) {
for (int i=0; i<mAdded.size(); i++) {
Fragment f = mAdded.get(i);
- if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) {
- show = true;
- f.onPrepareOptionsMenu(menu);
+ if (f != null) {
+ if (f.performPrepareOptionsMenu(menu)) {
+ show = true;
+ }
}
}
}
@@ -1909,8 +1944,8 @@ final class FragmentManagerImpl extends FragmentManager {
if (mAdded != null) {
for (int i=0; i<mAdded.size(); i++) {
Fragment f = mAdded.get(i);
- if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) {
- if (f.onOptionsItemSelected(item)) {
+ if (f != null) {
+ if (f.performOptionsItemSelected(item)) {
return true;
}
}
@@ -1923,8 +1958,8 @@ final class FragmentManagerImpl extends FragmentManager {
if (mAdded != null) {
for (int i=0; i<mAdded.size(); i++) {
Fragment f = mAdded.get(i);
- if (f != null && !f.mHidden && f.mUserVisibleHint) {
- if (f.onContextItemSelected(item)) {
+ if (f != null) {
+ if (f.performContextItemSelected(item)) {
return true;
}
}
@@ -1937,8 +1972,8 @@ final class FragmentManagerImpl extends FragmentManager {
if (mAdded != null) {
for (int i=0; i<mAdded.size(); i++) {
Fragment f = mAdded.get(i);
- if (f != null && !f.mHidden && f.mHasMenu && f.mMenuVisible) {
- f.onOptionsMenuClosed(menu);
+ if (f != null) {
+ f.performOptionsMenuClosed(menu);
}
}
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 031e39b..3124671 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -55,14 +55,18 @@ public interface IActivityManager extends IInterface {
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int flags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options) throws RemoteException;
+ public int startActivityAsUser(IApplicationThread caller,
+ Intent intent, String resolvedType, IBinder resultTo, String resultWho,
+ int requestCode, int flags, String profileFile,
+ ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;
public WaitResult startActivityAndWait(IApplicationThread caller,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int flags, String profileFile,
- ParcelFileDescriptor profileFd, Bundle options) throws RemoteException;
+ ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;
public int startActivityWithConfig(IApplicationThread caller,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, Configuration newConfig,
- Bundle options) throws RemoteException;
+ Bundle options, int userId) throws RemoteException;
public int startActivityIntentSender(IApplicationThread caller,
IntentSender intent, Intent fillInIntent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode,
@@ -76,34 +80,31 @@ public interface IActivityManager extends IInterface {
public boolean willActivityBeVisible(IBinder token) throws RemoteException;
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
IIntentReceiver receiver, IntentFilter filter,
- String requiredPermission) throws RemoteException;
+ String requiredPermission, int userId) throws RemoteException;
public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException;
public int broadcastIntent(IApplicationThread caller, Intent intent,
String resolvedType, IIntentReceiver resultTo, int resultCode,
String resultData, Bundle map, String requiredPermission,
boolean serialized, boolean sticky, int userId) throws RemoteException;
public void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) throws RemoteException;
- /* oneway */
public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException;
public void attachApplication(IApplicationThread app) throws RemoteException;
- /* oneway */
+ public void activityResumed(IBinder token) throws RemoteException;
public void activityIdle(IBinder token, Configuration config,
boolean stopProfiling) throws RemoteException;
public void activityPaused(IBinder token) throws RemoteException;
- /* oneway */
public void activityStopped(IBinder token, Bundle state,
Bitmap thumbnail, CharSequence description) throws RemoteException;
- /* oneway */
public void activitySlept(IBinder token) throws RemoteException;
- /* oneway */
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,
IThumbnailReceiver receiver) throws RemoteException;
public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
- int flags) throws RemoteException;
+ 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<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
throws RemoteException;
@@ -116,8 +117,8 @@ public interface IActivityManager extends IInterface {
public void reportThumbnail(IBinder token,
Bitmap thumbnail, CharSequence description) throws RemoteException;
public ContentProviderHolder getContentProvider(IApplicationThread caller,
- String name, boolean stable) throws RemoteException;
- public ContentProviderHolder getContentProviderExternal(String name, IBinder token)
+ String name, int userId, boolean stable) throws RemoteException;
+ public ContentProviderHolder getContentProviderExternal(String name, int userId, IBinder token)
throws RemoteException;
public void removeContentProvider(IBinder connection, boolean stable) throws RemoteException;
public void removeContentProviderExternal(String name, IBinder token) throws RemoteException;
@@ -129,9 +130,9 @@ public interface IActivityManager extends IInterface {
public PendingIntent getRunningServiceControlPanel(ComponentName service)
throws RemoteException;
public ComponentName startService(IApplicationThread caller, Intent service,
- String resolvedType) throws RemoteException;
+ String resolvedType, int userId) throws RemoteException;
public int stopService(IApplicationThread caller, Intent service,
- String resolvedType) throws RemoteException;
+ String resolvedType, int userId) throws RemoteException;
public boolean stopServiceToken(ComponentName className, IBinder token,
int startId) throws RemoteException;
public void setServiceForeground(ComponentName className, IBinder token,
@@ -156,7 +157,7 @@ public interface IActivityManager extends IInterface {
public void killApplicationProcess(String processName, int uid) throws RemoteException;
public boolean startInstrumentation(ComponentName className, String profileFile,
- int flags, Bundle arguments, IInstrumentationWatcher watcher)
+ int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId)
throws RemoteException;
public void finishInstrumentation(IApplicationThread target,
int resultCode, Bundle results) throws RemoteException;
@@ -173,13 +174,16 @@ public interface IActivityManager extends IInterface {
public IIntentSender getIntentSender(int type,
String packageName, IBinder token, String resultWho,
int requestCode, Intent[] intents, String[] resolvedTypes,
- int flags, Bundle options) throws RemoteException;
+ int flags, Bundle options, int userId) throws RemoteException;
public void cancelIntentSender(IIntentSender sender) throws RemoteException;
public boolean clearApplicationUserData(final String packageName,
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;
@@ -201,9 +205,10 @@ public interface IActivityManager extends IInterface {
public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException;
- public void killBackgroundProcesses(final String packageName) throws RemoteException;
+ public void killBackgroundProcesses(final String packageName, int userId)
+ throws RemoteException;
public void killAllBackgroundProcesses() throws RemoteException;
- public void forceStopPackage(final String packageName) throws RemoteException;
+ public void forceStopPackage(final String packageName, int userId) throws RemoteException;
// Note: probably don't want to allow applications access to these.
public void goingToSleep() throws RemoteException;
@@ -260,7 +265,7 @@ public interface IActivityManager extends IInterface {
public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException;
// Turn on/off profiling in a particular process.
- public boolean profileControl(String process, boolean start,
+ public boolean profileControl(String process, int userId, boolean start,
String path, ParcelFileDescriptor fd, int profileType) throws RemoteException;
public boolean shutdown(int timeout) throws RemoteException;
@@ -268,12 +273,7 @@ public interface IActivityManager extends IInterface {
public void stopAppSwitches() throws RemoteException;
public void resumeAppSwitches() throws RemoteException;
- public int startActivityInPackage(int uid,
- Intent intent, String resolvedType, IBinder resultTo,
- String resultWho, int requestCode, int startFlags, Bundle options)
- throws RemoteException;
-
- public void killApplicationWithUid(String pkg, int uid) throws RemoteException;
+ public void killApplicationWithAppId(String pkg, int appid) throws RemoteException;
public void closeSystemDialogs(String reason) throws RemoteException;
@@ -294,7 +294,7 @@ public interface IActivityManager extends IInterface {
public void crashApplication(int uid, int initialPid, String packageName,
String message) throws RemoteException;
- public String getProviderMimeType(Uri uri) throws RemoteException;
+ public String getProviderMimeType(Uri uri, int userId) throws RemoteException;
public IBinder newUriPermissionOwner(String name) throws RemoteException;
public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg,
@@ -306,15 +306,12 @@ public interface IActivityManager extends IInterface {
Uri uri, int modeFlags) throws RemoteException;
// Cause the specified process to dump the specified heap.
- public boolean dumpHeap(String process, boolean managed, String path,
+ public boolean dumpHeap(String process, int userId, boolean managed, String path,
ParcelFileDescriptor fd) throws RemoteException;
public int startActivities(IApplicationThread caller,
Intent[] intents, String[] resolvedTypes, IBinder resultTo,
- Bundle options) throws RemoteException;
- public int startActivitiesInPackage(int uid,
- Intent[] intents, String[] resolvedTypes, IBinder resultTo,
- Bundle options) throws RemoteException;
+ Bundle options, int userId) throws RemoteException;
public int getFrontActivityScreenCompatMode() throws RemoteException;
public void setFrontActivityScreenCompatMode(int mode) throws RemoteException;
@@ -327,7 +324,10 @@ public interface IActivityManager extends IInterface {
// Multi-user APIs
public boolean switchUser(int userid) throws RemoteException;
+ public int stopUser(int userid, IStopUserCallback callback) throws RemoteException;
public UserInfo getCurrentUser() throws RemoteException;
+ public boolean isUserRunning(int userid) throws RemoteException;
+ public int[] getRunningUserIds() throws RemoteException;
public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException;
@@ -358,6 +358,11 @@ public interface IActivityManager extends IInterface {
// manage your activity to make sure it is always the uid you expect.
public int getLaunchedFromUid(IBinder activityToken) throws RemoteException;
+ public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException;
+ public void unregisterUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException;
+
+ public void requestBugReport() throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -491,7 +496,7 @@ public interface IActivityManager extends IInterface {
int BIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+35;
int UNBIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+36;
int PUBLISH_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+37;
-
+ int ACTIVITY_RESUMED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+38;
int GOING_TO_SLEEP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+39;
int WAKING_UP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+40;
int SET_DEBUG_APP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+41;
@@ -546,10 +551,9 @@ public interface IActivityManager extends IInterface {
int BACKUP_AGENT_CREATED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+90;
int UNBIND_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+91;
int GET_UID_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+92;
-
-
- int START_ACTIVITY_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+94;
- int KILL_APPLICATION_WITH_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+95;
+ int HANDLE_INCOMING_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+93;
+ int GET_TASK_TOP_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+94;
+ int KILL_APPLICATION_WITH_APPID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+95;
int CLOSE_SYSTEM_DIALOGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+96;
int GET_PROCESS_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+97;
int KILL_APPLICATION_PROCESS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+98;
@@ -575,7 +579,7 @@ public interface IActivityManager extends IInterface {
int CHECK_GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118;
int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+119;
int START_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+120;
- int START_ACTIVITIES_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+121;
+ int IS_USER_RUNNING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+121;
int ACTIVITY_SLEPT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+122;
int GET_FRONT_ACTIVITY_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+123;
int SET_FRONT_ACTIVITY_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+124;
@@ -606,4 +610,10 @@ public interface IActivityManager extends IInterface {
int GET_LAUNCHED_FROM_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+149;
int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+150;
int IS_INTENT_SENDER_AN_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+151;
+ int START_ACTIVITY_AS_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+152;
+ int STOP_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+153;
+ int REGISTER_USER_SWITCH_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+154;
+ int UNREGISTER_USER_SWITCH_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+155;
+ int GET_RUNNING_USER_IDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+156;
+ int REQUEST_BUG_REPORT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+157;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index f60cfd6..03a26d4 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -65,7 +65,8 @@ public interface IApplicationThread extends IInterface {
void scheduleDestroyActivity(IBinder token, boolean finished,
int configChanges) throws RemoteException;
void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
- int resultCode, String data, Bundle extras, boolean sync) throws RemoteException;
+ int resultCode, String data, Bundle extras, boolean sync,
+ int sendingUser) throws RemoteException;
static final int BACKUP_MODE_INCREMENTAL = 0;
static final int BACKUP_MODE_FULL = 1;
static final int BACKUP_MODE_RESTORE = 2;
@@ -105,8 +106,8 @@ public interface IApplicationThread extends IInterface {
void dumpProvider(FileDescriptor fd, IBinder servicetoken, String[] args)
throws RemoteException;
void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
- int resultCode, String data, Bundle extras, boolean ordered, boolean sticky)
- throws RemoteException;
+ int resultCode, String data, Bundle extras, boolean ordered,
+ boolean sticky, int sendingUser) throws RemoteException;
void scheduleLowMemory() throws RemoteException;
void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException;
void profilerControl(boolean start, String path, ParcelFileDescriptor fd, int profileType)
diff --git a/core/java/android/app/IInstrumentationWatcher.aidl b/core/java/android/app/IInstrumentationWatcher.aidl
index 405a3d8..6c8c4d6 100644
--- a/core/java/android/app/IInstrumentationWatcher.aidl
+++ b/core/java/android/app/IInstrumentationWatcher.aidl
@@ -21,7 +21,7 @@ import android.content.ComponentName;
import android.os.Bundle;
/** @hide */
-oneway interface IInstrumentationWatcher
+interface IInstrumentationWatcher
{
void instrumentationStatus(in ComponentName name, int resultCode,
in Bundle results);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 6f95e26..62d4962 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -24,16 +24,13 @@ import android.content.Intent;
/** {@hide} */
interface INotificationManager
{
- /** @deprecated use {@link #enqueueNotificationWithTag} instead */
- void enqueueNotification(String pkg, int id, in Notification notification, inout int[] idReceived);
- /** @deprecated use {@link #cancelNotificationWithTag} instead */
- void cancelNotification(String pkg, int id);
- void cancelAllNotifications(String pkg);
+ void cancelAllNotifications(String pkg, int userId);
void enqueueToast(String pkg, ITransientNotification callback, int duration);
void cancelToast(String pkg, ITransientNotification callback);
- void enqueueNotificationWithTag(String pkg, String tag, int id, in Notification notification, inout int[] idReceived);
- void cancelNotificationWithTag(String pkg, String tag, int id);
+ void enqueueNotificationWithTag(String pkg, String tag, int id,
+ in Notification notification, inout int[] idReceived, int userId);
+ void cancelNotificationWithTag(String pkg, String tag, int id, int userId);
void setNotificationsEnabledForPackage(String pkg, boolean enabled);
boolean areNotificationsEnabledForPackage(String pkg);
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index 688cdfd..074d343 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -30,4 +30,5 @@ interface ISearchManager {
List<ResolveInfo> getGlobalSearchActivities();
ComponentName getGlobalSearchActivity();
ComponentName getWebSearchActivity();
+ ComponentName getAssistIntent(int userHandle);
}
diff --git a/core/java/android/app/IStopUserCallback.aidl b/core/java/android/app/IStopUserCallback.aidl
new file mode 100644
index 0000000..19ac1d5
--- /dev/null
+++ b/core/java/android/app/IStopUserCallback.aidl
@@ -0,0 +1,27 @@
+/*
+** Copyright 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.app;
+
+/**
+ * Callback to find out when we have finished stopping a user.
+ * {@hide}
+ */
+interface IStopUserCallback
+{
+ void userStopped(int userId);
+ void userStopAborted(int userId);
+}
diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl
new file mode 100644
index 0000000..845897b
--- /dev/null
+++ b/core/java/android/app/IUserSwitchObserver.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.app;
+
+import android.os.IRemoteCallback;
+
+/** {@hide} */
+oneway interface IUserSwitchObserver {
+ void onUserSwitching(int newUserId, IRemoteCallback reply);
+ void onUserSwitchComplete(int newUserId);
+}
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 69f64a1..3efd3c0 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -52,6 +52,11 @@ interface IWallpaperManager {
void clearWallpaper();
/**
+ * Return whether there is a wallpaper set with the given name.
+ */
+ boolean hasNamedWallpaper(String name);
+
+ /**
* Sets the dimension hint for the wallpaper. These hints indicate the desired
* minimum width and height for the wallpaper.
*/
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index cad4b01..e0856ae 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -33,6 +33,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.AndroidRuntimeException;
import android.util.Log;
import android.view.IWindowManager;
@@ -1429,6 +1430,21 @@ public class Instrumentation {
*/
public void execStartActivities(Context who, IBinder contextThread,
IBinder token, Activity target, Intent[] intents, Bundle options) {
+ execStartActivitiesAsUser(who, contextThread, token, target, intents, options,
+ UserHandle.myUserId());
+ }
+
+ /**
+ * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+ * but accepts an array of activities to be started. Note that active
+ * {@link ActivityMonitor} objects only match against the first activity in
+ * the array.
+ *
+ * {@hide}
+ */
+ public void execStartActivitiesAsUser(Context who, IBinder contextThread,
+ IBinder token, Activity target, Intent[] intents, Bundle options,
+ int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
synchronized (mSync) {
@@ -1452,7 +1468,8 @@ public class Instrumentation {
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver());
}
int result = ActivityManagerNative.getDefault()
- .startActivities(whoThread, intents, resolvedTypes, token, options);
+ .startActivities(whoThread, intents, resolvedTypes, token, options,
+ userId);
checkStartActivityResult(result, intents[0]);
} catch (RemoteException e) {
}
@@ -1518,6 +1535,66 @@ public class Instrumentation {
return null;
}
+ /**
+ * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+ * but for starting as a particular user.
+ *
+ * @param who The Context from which the activity is being started.
+ * @param contextThread The main thread of the Context from which the activity
+ * is being started.
+ * @param token Internal token identifying to the system who is starting
+ * the activity; may be null.
+ * @param target Which fragment is performing the start (and thus receiving
+ * any result).
+ * @param intent The actual Intent to start.
+ * @param requestCode Identifier for this request's result; less than zero
+ * if the caller is not expecting a result.
+ *
+ * @return To force the return of a particular result, return an
+ * ActivityResult object containing the desired data; otherwise
+ * return null. The default implementation always returns null.
+ *
+ * @throws android.content.ActivityNotFoundException
+ *
+ * @see Activity#startActivity(Intent)
+ * @see Activity#startActivityForResult(Intent, int)
+ * @see Activity#startActivityFromChild
+ *
+ * {@hide}
+ */
+ public ActivityResult execStartActivity(
+ Context who, IBinder contextThread, IBinder token, Activity target,
+ Intent intent, int requestCode, Bundle options, UserHandle user) {
+ IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (mActivityMonitors != null) {
+ synchronized (mSync) {
+ final int N = mActivityMonitors.size();
+ for (int i=0; i<N; i++) {
+ final ActivityMonitor am = mActivityMonitors.get(i);
+ if (am.match(who, null, intent)) {
+ am.mHits++;
+ if (am.isBlocking()) {
+ return requestCode >= 0 ? am.getResult() : null;
+ }
+ break;
+ }
+ }
+ }
+ }
+ try {
+ intent.setAllowFds(false);
+ intent.migrateExtraStreamToClipData();
+ int result = ActivityManagerNative.getDefault()
+ .startActivityAsUser(whoThread, intent,
+ intent.resolveTypeIfNeeded(who.getContentResolver()),
+ token, target != null ? target.mEmbeddedID : null,
+ requestCode, 0, null, null, options, user.getIdentifier());
+ checkStartActivityResult(result, intent);
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
/*package*/ final void init(ActivityThread thread,
Context instrContext, Context appContext, ComponentName component,
IInstrumentationWatcher watcher) {
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index ef61af7..22a21cd 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -16,13 +16,12 @@
package android.app;
-import android.content.Context;
import android.os.Binder;
import android.os.RemoteException;
import android.os.IBinder;
-import android.os.ServiceManager;
import android.view.IWindowManager;
import android.view.IOnKeyguardExitResult;
+import android.view.WindowManagerGlobal;
/**
* Class that can be used to lock and unlock the keyboard. Get an instance of this
@@ -111,7 +110,7 @@ public class KeyguardManager {
KeyguardManager() {
- mWM = IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
+ mWM = WindowManagerGlobal.getWindowManagerService();
}
/**
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index be4b284..0a9ed58 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -37,10 +37,11 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.StrictMode;
import android.os.Trace;
-import android.os.UserId;
+import android.os.UserHandle;
import android.util.AndroidRuntimeException;
import android.util.Slog;
import android.view.CompatibilityInfoHolder;
+import android.view.Display;
import java.io.File;
import java.io.IOException;
@@ -120,8 +121,8 @@ public final class LoadedApk {
final int myUid = Process.myUid();
mResDir = aInfo.uid == myUid ? aInfo.sourceDir
: aInfo.publicSourceDir;
- if (!UserId.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) {
- aInfo.dataDir = PackageManager.getDataDirForUser(UserId.getUserId(myUid),
+ if (!UserHandle.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) {
+ aInfo.dataDir = PackageManager.getDataDirForUser(UserHandle.getUserId(myUid),
mPackageName);
}
mSharedLibraries = aInfo.sharedLibraryFiles;
@@ -139,7 +140,8 @@ public final class LoadedApk {
ContextImpl.createSystemContext(mainThread);
ActivityThread.mSystemContext.getResources().updateConfiguration(
mainThread.getConfiguration(),
- mainThread.getDisplayMetricsLocked(compatInfo, false),
+ mainThread.getDisplayMetricsLocked(
+ Display.DEFAULT_DISPLAY, compatInfo),
compatInfo);
//Slog.i(TAG, "Created system resources "
// + mSystemContext.getResources() + ": "
@@ -195,7 +197,7 @@ public final class LoadedApk {
ApplicationInfo ai = null;
try {
ai = ActivityThread.getPackageManager().getApplicationInfo(packageName,
- PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId());
+ PackageManager.GET_SHARED_LIBRARY_FILES, UserHandle.myUserId());
} catch (RemoteException e) {
throw new AssertionError(e);
}
@@ -358,7 +360,7 @@ public final class LoadedApk {
IPackageManager pm = ActivityThread.getPackageManager();
android.content.pm.PackageInfo pi;
try {
- pi = pm.getPackageInfo(mPackageName, 0, UserId.myUserId());
+ pi = pm.getPackageInfo(mPackageName, 0, UserHandle.myUserId());
} catch (RemoteException e) {
throw new AssertionError(e);
}
@@ -471,7 +473,8 @@ public final class LoadedApk {
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
- mResources = mainThread.getTopLevelResources(mResDir, this);
+ mResources = mainThread.getTopLevelResources(mResDir,
+ Display.DEFAULT_DISPLAY, null, this);
}
return mResources;
}
@@ -667,8 +670,8 @@ public final class LoadedApk {
mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
mStrongRef = strong ? rd : null;
}
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered, boolean sticky) {
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
LoadedApk.ReceiverDispatcher rd = mDispatcher.get();
if (ActivityThread.DEBUG_BROADCAST) {
int seq = intent.getIntExtra("seq", -1);
@@ -677,7 +680,7 @@ public final class LoadedApk {
}
if (rd != null) {
rd.performReceive(intent, resultCode, data, extras,
- ordered, sticky);
+ ordered, sticky, sendingUser);
} else {
// The activity manager dispatched a broadcast to a registered
// receiver in this process, but before it could be delivered the
@@ -713,10 +716,10 @@ public final class LoadedApk {
private final boolean mOrdered;
public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras,
- boolean ordered, boolean sticky) {
+ boolean ordered, boolean sticky, int sendingUser) {
super(resultCode, resultData, resultExtras,
mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED,
- ordered, sticky, mIIntentReceiver.asBinder());
+ ordered, sticky, mIIntentReceiver.asBinder(), sendingUser);
mCurIntent = intent;
mOrdered = ordered;
}
@@ -827,14 +830,15 @@ public final class LoadedApk {
return mUnregisterLocation;
}
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered, boolean sticky) {
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
if (ActivityThread.DEBUG_BROADCAST) {
int seq = intent.getIntExtra("seq", -1);
Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq
+ " to " + mReceiver);
}
- Args args = new Args(intent, resultCode, data, extras, ordered, sticky);
+ Args args = new Args(intent, resultCode, data, extras, ordered,
+ sticky, sendingUser);
if (!mActivityThread.post(args)) {
if (mRegistered && ordered) {
IActivityManager mgr = ActivityManagerNative.getDefault();
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index ff71ee7..fd0f0bf 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -17,7 +17,6 @@
package android.app;
import android.content.Loader;
-import android.content.Loader.OnLoadCanceledListener;
import android.os.Bundle;
import android.util.DebugUtils;
import android.util.Log;
@@ -213,6 +212,8 @@ class LoaderManagerImpl extends LoaderManager {
// previously run loader until the new loader's data is available.
final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>();
+ final String mWho;
+
Activity mActivity;
boolean mStarted;
boolean mRetaining;
@@ -529,7 +530,8 @@ class LoaderManagerImpl extends LoaderManager {
}
}
- LoaderManagerImpl(Activity activity, boolean started) {
+ LoaderManagerImpl(String who, Activity activity, boolean started) {
+ mWho = who;
mActivity = activity;
mStarted = started;
}
diff --git a/core/java/android/app/MediaRouteActionProvider.java b/core/java/android/app/MediaRouteActionProvider.java
index c2f5ac1..63b641c 100644
--- a/core/java/android/app/MediaRouteActionProvider.java
+++ b/core/java/android/app/MediaRouteActionProvider.java
@@ -26,6 +26,7 @@ import android.util.Log;
import android.view.ActionProvider;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
import java.lang.ref.WeakReference;
@@ -80,8 +81,11 @@ public class MediaRouteActionProvider extends ActionProvider {
}
mMenuItem = item;
mView = new MediaRouteButton(mContext);
+ mView.setCheatSheetEnabled(true);
mView.setRouteTypes(mRouteTypes);
mView.setExtendedSettingsClickListener(mExtendedSettingsListener);
+ mView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
return mView;
}
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index b0bfe74..3ecafc3 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -23,14 +23,19 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteGroup;
import android.media.MediaRouter.RouteInfo;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.Gravity;
+import android.view.HapticFeedbackConstants;
import android.view.SoundEffectConstants;
import android.view.View;
+import android.widget.Toast;
public class MediaRouteButton extends View {
private static final String TAG = "MediaRouteButton";
@@ -44,6 +49,8 @@ public class MediaRouteButton extends View {
private Drawable mRemoteIndicator;
private boolean mRemoteActive;
private boolean mToggleMode;
+ private boolean mCheatSheetEnabled;
+ private boolean mIsConnecting;
private int mMinWidth;
private int mMinHeight;
@@ -51,6 +58,10 @@ public class MediaRouteButton extends View {
private OnClickListener mExtendedSettingsClickListener;
private MediaRouteChooserDialogFragment mDialogFragment;
+ private static final int[] CHECKED_STATE_SET = {
+ R.attr.state_checked
+ };
+
private static final int[] ACTIVATED_STATE_SET = {
R.attr.state_activated
};
@@ -82,6 +93,7 @@ public class MediaRouteButton extends View {
a.recycle();
setClickable(true);
+ setLongClickable(true);
setRouteTypes(routeTypes);
}
@@ -129,6 +141,52 @@ public class MediaRouteButton extends View {
return handled;
}
+ void setCheatSheetEnabled(boolean enable) {
+ mCheatSheetEnabled = enable;
+ }
+
+ @Override
+ public boolean performLongClick() {
+ if (super.performLongClick()) {
+ return true;
+ }
+
+ if (!mCheatSheetEnabled) {
+ return false;
+ }
+
+ final CharSequence contentDesc = getContentDescription();
+ if (TextUtils.isEmpty(contentDesc)) {
+ // Don't show the cheat sheet if we have no description
+ return false;
+ }
+
+ final int[] screenPos = new int[2];
+ final Rect displayFrame = new Rect();
+ getLocationOnScreen(screenPos);
+ getWindowVisibleDisplayFrame(displayFrame);
+
+ final Context context = getContext();
+ final int width = getWidth();
+ final int height = getHeight();
+ final int midy = screenPos[1] + height / 2;
+ final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
+
+ Toast cheatSheet = Toast.makeText(context, contentDesc, Toast.LENGTH_SHORT);
+ if (midy < displayFrame.height()) {
+ // Show along the top; follow action buttons
+ cheatSheet.setGravity(Gravity.TOP | Gravity.END,
+ screenWidth - screenPos[0] - width / 2, height);
+ } else {
+ // Show along the bottom center
+ cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
+ }
+ cheatSheet.show();
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+
+ return true;
+ }
+
public void setRouteTypes(int types) {
if (types == mRouteTypes) {
// Already registered; nothing to do.
@@ -157,10 +215,21 @@ public class MediaRouteButton extends View {
}
void updateRemoteIndicator() {
- final boolean isRemote =
- mRouter.getSelectedRoute(mRouteTypes) != mRouter.getSystemAudioRoute();
+ final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes);
+ final boolean isRemote = selected != mRouter.getSystemAudioRoute();
+ final boolean isConnecting = selected.getStatusCode() == RouteInfo.STATUS_CONNECTING;
+
+ boolean needsRefresh = false;
if (mRemoteActive != isRemote) {
mRemoteActive = isRemote;
+ needsRefresh = true;
+ }
+ if (mIsConnecting != isConnecting) {
+ mIsConnecting = isConnecting;
+ needsRefresh = true;
+ }
+
+ if (needsRefresh) {
refreshDrawableState();
}
}
@@ -168,27 +237,41 @@ public class MediaRouteButton extends View {
void updateRouteCount() {
final int N = mRouter.getRouteCount();
int count = 0;
+ boolean hasVideoRoutes = false;
for (int i = 0; i < N; i++) {
final RouteInfo route = mRouter.getRouteAt(i);
- if ((route.getSupportedTypes() & mRouteTypes) != 0) {
+ final int routeTypes = route.getSupportedTypes();
+ if ((routeTypes & mRouteTypes) != 0) {
if (route instanceof RouteGroup) {
count += ((RouteGroup) route).getRouteCount();
} else {
count++;
}
+ if ((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0) {
+ hasVideoRoutes = true;
+ }
}
}
setEnabled(count != 0);
- // Only allow toggling if we have more than just user routes
- mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0;
+ // Only allow toggling if we have more than just user routes.
+ // Don't toggle if we support video routes, we may have to let the dialog scan.
+ mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0 &&
+ !hasVideoRoutes;
}
@Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
- if (mRemoteActive) {
+
+ // Technically we should be handling this more completely, but these
+ // are implementation details here. Checked is used to express the connecting
+ // drawable state and it's mutually exclusive with activated for the purposes
+ // of state selection here.
+ if (mIsConnecting) {
+ mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+ } else if (mRemoteActive) {
mergeDrawableStates(drawableState, ACTIVATED_STATE_SET);
}
return drawableState;
@@ -366,6 +449,11 @@ public class MediaRouteButton extends View {
}
@Override
+ public void onRouteChanged(MediaRouter router, RouteInfo info) {
+ updateRemoteIndicator();
+ }
+
+ @Override
public void onRouteAdded(MediaRouter router, RouteInfo info) {
updateRouteCount();
}
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index 35cc324..396f910 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -1,6 +1,5 @@
package android.app;
-import com.android.internal.view.IInputMethodCallback;
import com.android.internal.view.IInputMethodSession;
import android.content.Context;
@@ -119,7 +118,7 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
}
}
- static class InputMethodCallback extends IInputMethodCallback.Stub {
+ static final class InputMethodCallback implements InputMethodManager.FinishedEventCallback {
WeakReference<NativeActivity> mNa;
InputMethodCallback(NativeActivity na) {
@@ -133,11 +132,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
na.finishPreDispatchKeyEventNative(na.mNativeHandle, seq, handled);
}
}
-
- @Override
- public void sessionCreated(IInputMethodSession session) {
- // Stub -- not for use in the client.
- }
}
@Override
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index cb83dc2..182ebef 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -29,6 +29,7 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.IntProperty;
import android.util.Log;
@@ -893,6 +894,19 @@ public class Notification implements Parcelable
return sb.toString();
}
+ /** {@hide} */
+ public void setUser(UserHandle user) {
+ if (tickerView != null) {
+ tickerView.setUser(user);
+ }
+ if (contentView != null) {
+ contentView.setUser(user);
+ }
+ if (bigContentView != null) {
+ bigContentView.setUser(user);
+ }
+ }
+
/**
* Builder class for {@link Notification} objects.
*
@@ -951,6 +965,7 @@ public class Notification implements Parcelable
private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
private boolean mUseChronometer;
private Style mStyle;
+ private boolean mShowWhen = true;
/**
* Constructs a new Builder with the defaults:
@@ -982,8 +997,9 @@ public class Notification implements Parcelable
/**
* Add a timestamp pertaining to the notification (usually the time the event occurred).
+ * It will be shown in the notification content view by default; use
+ * {@link Builder#setShowWhen(boolean) setShowWhen} to control this.
*
-
* @see Notification#when
*/
public Builder setWhen(long when) {
@@ -992,6 +1008,15 @@ public class Notification implements Parcelable
}
/**
+ * Control whether the timestamp set with {@link Builder#setWhen(long) setWhen} is shown
+ * in the content view.
+ */
+ public Builder setShowWhen(boolean show) {
+ mShowWhen = show;
+ return this;
+ }
+
+ /**
* Show the {@link Notification#when} field as a stopwatch.
*
* Instead of presenting <code>when</code> as a timestamp, the notification will show an
@@ -1467,7 +1492,7 @@ public class Notification implements Parcelable
contentView.setViewPadding(R.id.line1, 0, 0, 0, 0);
}
- if (mWhen != 0) {
+ if (mWhen != 0 && mShowWhen) {
if (mUseChronometer) {
contentView.setViewVisibility(R.id.chronometer, View.VISIBLE);
contentView.setLong(R.id.chronometer, "setBase",
@@ -1477,7 +1502,10 @@ public class Notification implements Parcelable
contentView.setViewVisibility(R.id.time, View.VISIBLE);
contentView.setLong(R.id.time, "setTime", mWhen);
}
+ } else {
+ contentView.setViewVisibility(R.id.time, View.GONE);
}
+
contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE);
contentView.setViewVisibility(R.id.overflow_divider, showLine3 ? View.VISIBLE : View.GONE);
return contentView;
@@ -1923,6 +1951,7 @@ public class Notification implements Parcelable
contentView.setViewVisibility(rowId, View.GONE);
}
+
int i=0;
while (i < mTexts.size() && i < rowIds.length) {
CharSequence str = mTexts.get(i);
@@ -1933,11 +1962,11 @@ public class Notification implements Parcelable
i++;
}
- if (mTexts.size() > rowIds.length) {
- contentView.setViewVisibility(R.id.inbox_more, View.VISIBLE);
- } else {
- contentView.setViewVisibility(R.id.inbox_more, View.GONE);
- }
+ contentView.setViewVisibility(R.id.inbox_end_pad,
+ mTexts.size() > 0 ? View.VISIBLE : View.GONE);
+
+ contentView.setViewVisibility(R.id.inbox_more,
+ mTexts.size() > rowIds.length ? View.VISIBLE : View.GONE);
return contentView;
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index bf83f5e..0acad75 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -17,11 +17,11 @@
package android.app;
import android.content.Context;
-import android.os.Binder;
-import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.util.Log;
/**
@@ -88,6 +88,11 @@ public class NotificationManager
mContext = context;
}
+ /** {@hide} */
+ public static NotificationManager from(Context context) {
+ return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
/**
* Post a notification to be shown in the status bar. If a notification with
* the same id has already been posted by your application and has not yet been canceled, it
@@ -119,9 +124,35 @@ public class NotificationManager
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
+ if (notification.sound != null) {
+ notification.sound = notification.sound.getCanonicalUri();
+ }
+ if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
+ try {
+ service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
+ UserHandle.myUserId());
+ if (id != idOut[0]) {
+ Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
+ {
+ int[] idOut = new int[1];
+ INotificationManager service = getService();
+ String pkg = mContext.getPackageName();
+ if (notification.sound != null) {
+ notification.sound = notification.sound.getCanonicalUri();
+ }
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
try {
- service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut);
+ service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
+ user.getIdentifier());
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
@@ -150,7 +181,21 @@ public class NotificationManager
String pkg = mContext.getPackageName();
if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")");
try {
- service.cancelNotificationWithTag(pkg, tag, id);
+ service.cancelNotificationWithTag(pkg, tag, id, UserHandle.myUserId());
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void cancelAsUser(String tag, int id, UserHandle user)
+ {
+ INotificationManager service = getService();
+ String pkg = mContext.getPackageName();
+ if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")");
+ try {
+ service.cancelNotificationWithTag(pkg, tag, id, user.getIdentifier());
} catch (RemoteException e) {
}
}
@@ -165,7 +210,7 @@ public class NotificationManager
String pkg = mContext.getPackageName();
if (localLOGV) Log.v(TAG, pkg + ": cancelAll()");
try {
- service.cancelAllNotifications(pkg);
+ service.cancelAllNotifications(pkg, UserHandle.myUserId());
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 8adc8a2..d36d99d 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -27,12 +27,13 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.AndroidException;
/**
* A description of an Intent and target action to perform with it. Instances
- * of this class are created with {@link #getActivity},
- * {@link #getBroadcast}, {@link #getService}; the returned object can be
+ * of this class are created with {@link #getActivity}, {@link #getActivities},
+ * {@link #getBroadcast}, and {@link #getService}; the returned object can be
* handed to other applications so that they can perform the action you
* described on your behalf at a later time.
*
@@ -53,6 +54,34 @@ import android.util.AndroidException;
* categories, and components, and same flags), it will receive a PendingIntent
* representing the same token if that is still valid, and can thus call
* {@link #cancel} to remove it.
+ *
+ * <p>Because of this behavior, it is important to know when two Intents
+ * are considered to be the same for purposes of retrieving a PendingIntent.
+ * A common mistake people make is to create multiple PendingIntent objects
+ * with Intents that only vary in their "extra" contents, expecting to get
+ * a different PendingIntent each time. This does <em>not</em> happen. The
+ * parts of the Intent that are used for matching are the same ones defined
+ * by {@link Intent#filterEquals(Intent) Intent.filterEquals}. If you use two
+ * Intent objects that are equivalent as per
+ * {@link Intent#filterEquals(Intent) Intent.filterEquals}, then you will get
+ * the same PendingIntent for both of them.
+ *
+ * <p>There are two typical ways to deal with this.
+ *
+ * <p>If you truly need multiple distinct PendingIntent objects active at
+ * the same time (such as to use as two notifications that are both shown
+ * at the same time), then you will need to ensure there is something that
+ * is different about them to associate them with different PendingIntents.
+ * This may be any of the Intent attributes considered by
+ * {@link Intent#filterEquals(Intent) Intent.filterEquals}, or different
+ * request code integers supplied to {@link #getActivity}, {@link #getActivities},
+ * {@link #getBroadcast}, or {@link #getService}.
+ *
+ * <p>If you only need one PendingIntent active at a time for any of the
+ * Intents you will use, then you can alternatively use the flags
+ * {@link #FLAG_CANCEL_CURRENT} or {@link #FLAG_UPDATE_CURRENT} to either
+ * cancel or modify whatever current PendingIntent is associated with the
+ * Intent you are supplying.
*/
public final class PendingIntent implements Parcelable {
private final IIntentSender mTarget;
@@ -146,8 +175,8 @@ public final class PendingIntent implements Parcelable {
mWho = who;
mHandler = handler;
}
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean serialized, boolean sticky) {
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean serialized, boolean sticky, int sendingUser) {
mIntent = intent;
mResultCode = resultCode;
mResultData = data;
@@ -227,7 +256,31 @@ public final class PendingIntent implements Parcelable {
ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
null, null, requestCode, new Intent[] { intent },
resolvedType != null ? new String[] { resolvedType } : null,
- flags, options);
+ flags, options, UserHandle.myUserId());
+ return target != null ? new PendingIntent(target) : null;
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ * Note that UserHandle.CURRENT will be interpreted at the time the
+ * activity is started, not when the pending intent is created.
+ */
+ public static PendingIntent getActivityAsUser(Context context, int requestCode,
+ Intent intent, int flags, Bundle options, UserHandle user) {
+ String packageName = context.getPackageName();
+ String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
+ context.getContentResolver()) : null;
+ try {
+ intent.setAllowFds(false);
+ IIntentSender target =
+ ActivityManagerNative.getDefault().getIntentSender(
+ ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
+ null, null, requestCode, new Intent[] { intent },
+ resolvedType != null ? new String[] { resolvedType } : null,
+ flags, options, user.getIdentifier());
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
}
@@ -333,7 +386,33 @@ public final class PendingIntent implements Parcelable {
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
- null, null, requestCode, intents, resolvedTypes, flags, options);
+ null, null, requestCode, intents, resolvedTypes, flags, options,
+ UserHandle.myUserId());
+ return target != null ? new PendingIntent(target) : null;
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ * Note that UserHandle.CURRENT will be interpreted at the time the
+ * activity is started, not when the pending intent is created.
+ */
+ public static PendingIntent getActivitiesAsUser(Context context, int requestCode,
+ Intent[] intents, int flags, Bundle options, UserHandle user) {
+ String packageName = context.getPackageName();
+ String[] resolvedTypes = new String[intents.length];
+ for (int i=0; i<intents.length; i++) {
+ intents[i].setAllowFds(false);
+ resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver());
+ }
+ try {
+ IIntentSender target =
+ ActivityManagerNative.getDefault().getIntentSender(
+ ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
+ null, null, requestCode, intents, resolvedTypes,
+ flags, options, user.getIdentifier());
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
}
@@ -361,6 +440,17 @@ public final class PendingIntent implements Parcelable {
*/
public static PendingIntent getBroadcast(Context context, int requestCode,
Intent intent, int flags) {
+ return getBroadcastAsUser(context, requestCode, intent, flags,
+ new UserHandle(UserHandle.myUserId()));
+ }
+
+ /**
+ * @hide
+ * Note that UserHandle.CURRENT will be interpreted at the time the
+ * broadcast is sent, not when the pending intent is created.
+ */
+ public static PendingIntent getBroadcastAsUser(Context context, int requestCode,
+ Intent intent, int flags, UserHandle userHandle) {
String packageName = context.getPackageName();
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
@@ -371,7 +461,7 @@ public final class PendingIntent implements Parcelable {
ActivityManager.INTENT_SENDER_BROADCAST, packageName,
null, null, requestCode, new Intent[] { intent },
resolvedType != null ? new String[] { resolvedType } : null,
- flags, null);
+ flags, null, userHandle.getIdentifier());
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
}
@@ -410,7 +500,7 @@ public final class PendingIntent implements Parcelable {
ActivityManager.INTENT_SENDER_SERVICE, packageName,
null, null, requestCode, new Intent[] { intent },
resolvedType != null ? new String[] { resolvedType } : null,
- flags, null);
+ flags, null, UserHandle.myUserId());
return target != null ? new PendingIntent(target) : null;
} catch (RemoteException e) {
}
@@ -598,6 +688,20 @@ public final class PendingIntent implements Parcelable {
}
/**
+ * @deprecated Renamed to {@link #getCreatorPackage()}.
+ */
+ @Deprecated
+ public String getTargetPackage() {
+ try {
+ return ActivityManagerNative.getDefault()
+ .getPackageForIntentSender(mTarget);
+ } catch (RemoteException e) {
+ // Should never happen.
+ return null;
+ }
+ }
+
+ /**
* Return the package name of the application that created this
* PendingIntent, that is the identity under which you will actually be
* sending the Intent. The returned string is supplied by the system, so
@@ -606,7 +710,7 @@ public final class PendingIntent implements Parcelable {
* @return The package name of the PendingIntent, or null if there is
* none associated with it.
*/
- public String getTargetPackage() {
+ public String getCreatorPackage() {
try {
return ActivityManagerNative.getDefault()
.getPackageForIntentSender(mTarget);
@@ -617,6 +721,47 @@ public final class PendingIntent implements Parcelable {
}
/**
+ * Return the uid of the application that created this
+ * PendingIntent, that is the identity under which you will actually be
+ * sending the Intent. The returned integer is supplied by the system, so
+ * that an application can not spoof its uid.
+ *
+ * @return The uid of the PendingIntent, or -1 if there is
+ * none associated with it.
+ */
+ public int getCreatorUid() {
+ try {
+ return ActivityManagerNative.getDefault()
+ .getUidForIntentSender(mTarget);
+ } catch (RemoteException e) {
+ // Should never happen.
+ return -1;
+ }
+ }
+
+ /**
+ * Return the user handle of the application that created this
+ * PendingIntent, that is the user under which you will actually be
+ * sending the Intent. The returned UserHandle is supplied by the system, so
+ * that an application can not spoof its user. See
+ * {@link android.os.Process#myUserHandle() Process.myUserHandle()} for
+ * more explanation of user handles.
+ *
+ * @return The user handle of the PendingIntent, or null if there is
+ * none associated with it.
+ */
+ public UserHandle getCreatorUserHandle() {
+ try {
+ int uid = ActivityManagerNative.getDefault()
+ .getUidForIntentSender(mTarget);
+ return uid > 0 ? new UserHandle(UserHandle.getUserId(uid)) : null;
+ } catch (RemoteException e) {
+ // Should never happen.
+ return null;
+ }
+ }
+
+ /**
* @hide
* Check to verify that this PendingIntent targets a specific package.
*/
diff --git a/core/java/android/app/Presentation.java b/core/java/android/app/Presentation.java
new file mode 100644
index 0000000..b5e5244
--- /dev/null
+++ b/core/java/android/app/Presentation.java
@@ -0,0 +1,258 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.view.ContextThemeWrapper;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.WindowManagerImpl;
+import android.os.Handler;
+import android.os.Message;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+
+/**
+ * Base class for presentations.
+ * <p>
+ * A presentation is a special kind of dialog whose purpose is to present
+ * content on a secondary display. A {@link Presentation} is associated with
+ * the target {@link Display} at creation time and configures its context and
+ * resource configuration according to the display's metrics.
+ * </p><p>
+ * Notably, the {@link Context} of a presentation is different from the context
+ * of its containing {@link Activity}. It is important to inflate the layout
+ * of a presentation and load other resources using the presentation's own context
+ * to ensure that assets of the correct size and density for the target display
+ * are loaded.
+ * </p><p>
+ * A presentation is automatically canceled (see {@link Dialog#cancel()}) when
+ * the display to which it is attached is removed. An activity should take
+ * care of pausing and resuming whatever content is playing within the presentation
+ * whenever the activity itself is paused or resumed.
+ * </p>
+ *
+ * @see DisplayManager for information on how to enumerate displays and receive
+ * notifications when displays are added or removed.
+ */
+public class Presentation extends Dialog {
+ private static final String TAG = "Presentation";
+
+ private static final int MSG_CANCEL = 1;
+
+ private final Display mDisplay;
+ private final DisplayManager mDisplayManager;
+
+ /**
+ * Creates a new presentation that is attached to the specified display
+ * using the default theme.
+ *
+ * @param outerContext The context of the application that is showing the presentation.
+ * The presentation will create its own context (see {@link #getContext()}) based
+ * on this context and information about the associated display.
+ * @param display The display to which the presentation should be attached.
+ */
+ public Presentation(Context outerContext, Display display) {
+ this(outerContext, display, 0);
+ }
+
+ /**
+ * Creates a new presentation that is attached to the specified display
+ * using the optionally specified theme.
+ *
+ * @param outerContext The context of the application that is showing the presentation.
+ * The presentation will create its own context (see {@link #getContext()}) based
+ * on this context and information about the associated display.
+ * @param display The display to which the presentation should be attached.
+ * @param theme A style resource describing the theme to use for the window.
+ * See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">
+ * Style and Theme Resources</a> for more information about defining and using
+ * styles. This theme is applied on top of the current theme in
+ * <var>outerContext</var>. If 0, the default presentation theme will be used.
+ */
+ public Presentation(Context outerContext, Display display, int theme) {
+ super(createPresentationContext(outerContext, display, theme), theme, false);
+
+ mDisplay = display;
+ mDisplayManager = (DisplayManager)getContext().getSystemService(Context.DISPLAY_SERVICE);
+
+ getWindow().setGravity(Gravity.FILL);
+ setCanceledOnTouchOutside(false);
+ }
+
+ /**
+ * Gets the {@link Display} that this presentation appears on.
+ *
+ * @return The display.
+ */
+ public Display getDisplay() {
+ return mDisplay;
+ }
+
+ /**
+ * Gets the {@link Resources} that should be used to inflate the layout of this presentation.
+ * This resources object has been configured according to the metrics of the
+ * display that the presentation appears on.
+ *
+ * @return The presentation resources object.
+ */
+ public Resources getResources() {
+ return getContext().getResources();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mDisplayManager.registerDisplayListener(mDisplayListener, null);
+
+ // Since we were not watching for display changes until just now, there is a
+ // chance that the display metrics have changed. If so, we will need to
+ // dismiss the presentation immediately. This case is expected
+ // to be rare but surprising, so we'll write a log message about it.
+ if (!isConfigurationStillValid()) {
+ Log.i(TAG, "Presentation is being immediately dismissed because the "
+ + "display metrics have changed since it was created.");
+ mHandler.sendEmptyMessage(MSG_CANCEL);
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ mDisplayManager.unregisterDisplayListener(mDisplayListener);
+ super.onStop();
+ }
+
+ /**
+ * Called by the system when the {@link Display} to which the presentation
+ * is attached has been removed.
+ *
+ * The system automatically calls {@link #cancel} to dismiss the presentation
+ * after sending this event.
+ *
+ * @see #getDisplay
+ */
+ public void onDisplayRemoved() {
+ }
+
+ /**
+ * Called by the system when the properties of the {@link Display} to which
+ * the presentation is attached have changed.
+ *
+ * If the display metrics have changed (for example, if the display has been
+ * resized or rotated), then the system automatically calls
+ * {@link #cancel} to dismiss the presentation.
+ *
+ * @see #getDisplay
+ */
+ public void onDisplayChanged() {
+ }
+
+ private void handleDisplayRemoved() {
+ onDisplayRemoved();
+ cancel();
+ }
+
+ private void handleDisplayChanged() {
+ onDisplayChanged();
+
+ // We currently do not support configuration changes for presentations
+ // (although we could add that feature with a bit more work).
+ // If the display metrics have changed in any way then the current configuration
+ // is invalid and the application must recreate the presentation to get
+ // a new context.
+ if (!isConfigurationStillValid()) {
+ cancel();
+ }
+ }
+
+ private boolean isConfigurationStillValid() {
+ DisplayMetrics dm = new DisplayMetrics();
+ mDisplay.getMetrics(dm);
+ return dm.equals(getResources().getDisplayMetrics());
+ }
+
+ private static Context createPresentationContext(
+ Context outerContext, Display display, int theme) {
+ if (outerContext == null) {
+ throw new IllegalArgumentException("outerContext must not be null");
+ }
+ if (display == null) {
+ throw new IllegalArgumentException("display must not be null");
+ }
+
+ Context displayContext = outerContext.createDisplayContext(display);
+ if (theme == 0) {
+ TypedValue outValue = new TypedValue();
+ displayContext.getTheme().resolveAttribute(
+ com.android.internal.R.attr.presentationTheme, outValue, true);
+ theme = outValue.resourceId;
+ }
+
+ // Derive the display's window manager from the outer window manager.
+ // We do this because the outer window manager have some extra information
+ // such as the parent window, which is important if the presentation uses
+ // an application window type.
+ final WindowManagerImpl outerWindowManager =
+ (WindowManagerImpl)outerContext.getSystemService(Context.WINDOW_SERVICE);
+ final WindowManagerImpl displayWindowManager =
+ outerWindowManager.createPresentationWindowManager(display);
+ return new ContextThemeWrapper(displayContext, theme) {
+ @Override
+ public Object getSystemService(String name) {
+ if (Context.WINDOW_SERVICE.equals(name)) {
+ return displayWindowManager;
+ }
+ return super.getSystemService(name);
+ }
+ };
+ }
+
+ private final DisplayListener mDisplayListener = new DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ if (displayId == mDisplay.getDisplayId()) {
+ handleDisplayRemoved();
+ }
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == mDisplay.getDisplayId()) {
+ handleDisplayChanged();
+ }
+ }
+ };
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CANCEL:
+ cancel();
+ break;
+ }
+ }
+ };
+}
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index d1d5131..43a163d 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -31,6 +31,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
@@ -845,14 +846,28 @@ public class SearchManager
*
* @hide
*/
- public static final Intent getAssistIntent(Context context) {
- PackageManager pm = context.getPackageManager();
- Intent intent = new Intent(Intent.ACTION_ASSIST);
- ComponentName component = intent.resolveActivity(pm);
- if (component != null) {
- intent.setComponent(component);
+ public Intent getAssistIntent(Context context) {
+ return getAssistIntent(context, UserHandle.myUserId());
+ }
+
+ /**
+ * Gets an intent for launching installed assistant activity, or null if not available.
+ * @return The assist intent.
+ *
+ * @hide
+ */
+ public Intent getAssistIntent(Context context, int userHandle) {
+ try {
+ ComponentName comp = mService.getAssistIntent(userHandle);
+ if (comp == null) {
+ return null;
+ }
+ Intent intent = new Intent(Intent.ACTION_ASSIST);
+ intent.setComponent(comp);
return intent;
+ } catch (RemoteException re) {
+ Log.e(TAG, "getAssistIntent() failed: " + re);
+ return null;
}
- return null;
}
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index dd9f337..3d656c7 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -97,13 +97,13 @@ public class StatusBarManager {
}
/**
- * Expand the status bar.
+ * Expand the notifications panel.
*/
- public void expand() {
+ public void expandNotificationsPanel() {
try {
final IStatusBarService svc = getService();
if (svc != null) {
- svc.expand();
+ svc.expandNotificationsPanel();
}
} catch (RemoteException ex) {
// system process is dead anyway.
@@ -112,13 +112,28 @@ public class StatusBarManager {
}
/**
- * Collapse the status bar.
+ * Collapse the notifications and settings panels.
*/
- public void collapse() {
+ public void collapsePanels() {
try {
final IStatusBarService svc = getService();
if (svc != null) {
- svc.collapse();
+ svc.collapsePanels();
+ }
+ } catch (RemoteException ex) {
+ // system process is dead anyway.
+ throw new RuntimeException(ex);
+ }
+ }
+
+ /**
+ * Expand the settings panel.
+ */
+ public void expandSettingsPanel() {
+ try {
+ final IStatusBarService svc = getService();
+ if (svc != null) {
+ svc.expandSettingsPanel();
}
} catch (RemoteException ex) {
// system process is dead anyway.
diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java
index f21b3fd..3e0ac7e 100644
--- a/core/java/android/app/TaskStackBuilder.java
+++ b/core/java/android/app/TaskStackBuilder.java
@@ -23,6 +23,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
+import android.os.UserHandle;
import android.util.Log;
import java.util.ArrayList;
@@ -124,24 +125,16 @@ public class TaskStackBuilder {
* @return This TaskStackBuilder for method chaining
*/
public TaskStackBuilder addParentStack(Activity sourceActivity) {
- final int insertAt = mIntents.size();
- Intent parent = sourceActivity.getParentActivityIntent();
- PackageManager pm = sourceActivity.getPackageManager();
- while (parent != null) {
- mIntents.add(insertAt, parent);
- try {
- ActivityInfo info = pm.getActivityInfo(parent.getComponent(), 0);
- String parentActivity = info.parentActivityName;
- if (parentActivity != null) {
- parent = new Intent().setComponent(
- new ComponentName(mSourceContext, parentActivity));
- } else {
- parent = null;
- }
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Bad ComponentName while traversing activity parent metadata");
- throw new IllegalArgumentException(e);
+ final Intent parent = sourceActivity.getParentActivityIntent();
+ if (parent != null) {
+ // We have the actual parent intent, build the rest from static metadata
+ // then add the direct parent intent to the end.
+ ComponentName target = parent.getComponent();
+ if (target == null) {
+ target = parent.resolveActivity(mSourceContext.getPackageManager());
}
+ addParentStack(target);
+ addNextIntent(parent);
}
return this;
}
@@ -155,24 +148,7 @@ public class TaskStackBuilder {
* @return This TaskStackBuilder for method chaining
*/
public TaskStackBuilder addParentStack(Class<?> sourceActivityClass) {
- final int insertAt = mIntents.size();
- PackageManager pm = mSourceContext.getPackageManager();
- try {
- ActivityInfo info = pm.getActivityInfo(
- new ComponentName(mSourceContext, sourceActivityClass), 0);
- String parentActivity = info.parentActivityName;
- while (parentActivity != null) {
- Intent parent = new Intent().setComponent(
- new ComponentName(mSourceContext, parentActivity));
- mIntents.add(insertAt, parent);
- info = pm.getActivityInfo(parent.getComponent(), 0);
- parentActivity = info.parentActivityName;
- }
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Bad ComponentName while traversing activity parent metadata");
- throw new IllegalArgumentException(e);
- }
- return this;
+ return addParentStack(new ComponentName(mSourceContext, sourceActivityClass));
}
/**
@@ -191,11 +167,13 @@ public class TaskStackBuilder {
ActivityInfo info = pm.getActivityInfo(sourceActivityName, 0);
String parentActivity = info.parentActivityName;
while (parentActivity != null) {
- Intent parent = new Intent().setComponent(
- new ComponentName(info.packageName, parentActivity));
- mIntents.add(insertAt, parent);
- info = pm.getActivityInfo(parent.getComponent(), 0);
+ final ComponentName target = new ComponentName(info.packageName, parentActivity);
+ info = pm.getActivityInfo(target, 0);
parentActivity = info.parentActivityName;
+ final Intent parent = parentActivity == null && insertAt == 0
+ ? Intent.makeMainActivity(target)
+ : new Intent().setComponent(target);
+ mIntents.add(insertAt, parent);
}
} catch (NameNotFoundException e) {
Log.e(TAG, "Bad ComponentName while traversing activity parent metadata");
@@ -232,22 +210,26 @@ public class TaskStackBuilder {
/**
* Start the task stack constructed by this builder.
- *
- * @param options Additional options for how the Activity should be started.
- * See {@link android.content.Context#startActivity(Intent, Bundle)
- * Context.startActivity(Intent, Bundle)} for more details.
+ * @hide
*/
- public void startActivities(Bundle options) {
+ public void startActivities(Bundle options, UserHandle userHandle) {
if (mIntents.isEmpty()) {
throw new IllegalStateException(
"No intents added to TaskStackBuilder; cannot startActivities");
}
- Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
- intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_CLEAR_TASK |
- Intent.FLAG_ACTIVITY_TASK_ON_HOME);
- mSourceContext.startActivities(intents, options);
+ mSourceContext.startActivitiesAsUser(getIntents(), options, userHandle);
+ }
+
+ /**
+ * Start the task stack constructed by this builder.
+ *
+ * @param options Additional options for how the Activity should be started.
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details.
+ */
+ public void startActivities(Bundle options) {
+ startActivities(options, new UserHandle(UserHandle.myUserId()));
}
/**
@@ -287,11 +269,22 @@ public class TaskStackBuilder {
"No intents added to TaskStackBuilder; cannot getPendingIntent");
}
- Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
- intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_CLEAR_TASK |
- Intent.FLAG_ACTIVITY_TASK_ON_HOME);
- return PendingIntent.getActivities(mSourceContext, requestCode, intents, flags, options);
+ return PendingIntent.getActivities(mSourceContext, requestCode, getIntents(),
+ flags, options);
+ }
+
+ /**
+ * @hide
+ */
+ public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options,
+ UserHandle user) {
+ if (mIntents.isEmpty()) {
+ throw new IllegalStateException(
+ "No intents added to TaskStackBuilder; cannot getPendingIntent");
+ }
+
+ return PendingIntent.getActivitiesAsUser(mSourceContext, requestCode, getIntents(), flags,
+ options, user);
}
/**
@@ -302,6 +295,15 @@ public class TaskStackBuilder {
* @return An array containing the intents added to this builder.
*/
public Intent[] getIntents() {
- return mIntents.toArray(new Intent[mIntents.size()]);
+ Intent[] intents = new Intent[mIntents.size()];
+ if (intents.length == 0) return intents;
+
+ intents[0] = new Intent(mIntents.get(0)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK |
+ Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ for (int i = 1; i < intents.length; i++) {
+ intents[i] = new Intent(mIntents.get(i));
+ }
+ return intents;
}
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index c131549..9c0064e 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -42,6 +42,8 @@ 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.FileOutputStream;
import java.io.IOException;
@@ -241,7 +243,7 @@ public class WallpaperManager {
}
mWallpaper = null;
try {
- mWallpaper = getCurrentWallpaperLocked();
+ mWallpaper = getCurrentWallpaperLocked(context);
} catch (OutOfMemoryError e) {
Log.w(TAG, "No memory load current wallpaper", e);
}
@@ -264,7 +266,7 @@ public class WallpaperManager {
}
}
- private Bitmap getCurrentWallpaperLocked() {
+ private Bitmap getCurrentWallpaperLocked(Context context) {
try {
Bundle params = new Bundle();
ParcelFileDescriptor fd = mService.getWallpaper(this, params);
@@ -276,7 +278,7 @@ public class WallpaperManager {
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bm = BitmapFactory.decodeFileDescriptor(
fd.getFileDescriptor(), null, options);
- return generateBitmap(bm, width, height);
+ return generateBitmap(context, bm, width, height);
} catch (OutOfMemoryError e) {
Log.w(TAG, "Can't decode file", e);
} finally {
@@ -304,7 +306,7 @@ public class WallpaperManager {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bm = BitmapFactory.decodeStream(is, null, options);
- return generateBitmap(bm, width, height);
+ return generateBitmap(context, bm, width, height);
} catch (OutOfMemoryError e) {
Log.w(TAG, "Can't decode stream", e);
} finally {
@@ -588,6 +590,25 @@ public class WallpaperManager {
}
/**
+ * Return whether any users are currently set to use the wallpaper
+ * with the given resource ID. That is, their wallpaper has been
+ * set through {@link #setResource(int)} with the same resource id.
+ */
+ public boolean hasResourceWallpaper(int resid) {
+ if (sGlobals.mService == null) {
+ Log.w(TAG, "WallpaperService not running");
+ return false;
+ }
+ try {
+ Resources resources = mContext.getResources();
+ String name = "res:" + resources.getResourceName(resid);
+ return sGlobals.mService.hasNamedWallpaper(name);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
* Returns the desired minimum width for the wallpaper. Callers of
* {@link #setBitmap(android.graphics.Bitmap)} or
* {@link #setStream(java.io.InputStream)} should check this value
@@ -688,7 +709,7 @@ public class WallpaperManager {
public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
try {
//Log.v(TAG, "Sending new wallpaper offsets from app...");
- ViewRootImpl.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
+ WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep);
//Log.v(TAG, "...app returning after sending offsets!");
} catch (RemoteException e) {
@@ -726,7 +747,7 @@ public class WallpaperManager {
int x, int y, int z, Bundle extras) {
try {
//Log.v(TAG, "Sending new wallpaper offsets from app...");
- ViewRootImpl.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand(
+ WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand(
windowToken, action, x, y, z, extras, false);
//Log.v(TAG, "...app returning after sending offsets!");
} catch (RemoteException e) {
@@ -746,7 +767,7 @@ public class WallpaperManager {
*/
public void clearWallpaperOffsets(IBinder windowToken) {
try {
- ViewRootImpl.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
+ WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
windowToken, -1, -1, -1, -1);
} catch (RemoteException e) {
// Ignore.
@@ -768,12 +789,15 @@ public class WallpaperManager {
setResource(com.android.internal.R.drawable.default_wallpaper);
}
- static Bitmap generateBitmap(Bitmap bm, int width, int height) {
+ static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) {
if (bm == null) {
return null;
}
- bm.setDensity(DisplayMetrics.DENSITY_DEVICE);
+ WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ DisplayMetrics metrics = new DisplayMetrics();
+ wm.getDefaultDisplay().getMetrics(metrics);
+ bm.setDensity(metrics.noncompatDensityDpi);
if (width <= 0 || height <= 0
|| (bm.getWidth() == width && bm.getHeight() == height)) {
@@ -783,7 +807,7 @@ public class WallpaperManager {
// This is the final bitmap we want to return.
try {
Bitmap newbm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- newbm.setDensity(DisplayMetrics.DENSITY_DEVICE);
+ newbm.setDensity(metrics.noncompatDensityDpi);
Canvas c = new Canvas(newbm);
Rect targetRect = new Rect();
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 1c37414..b351811 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -50,23 +50,23 @@ import java.util.HashMap;
*/
public final class DeviceAdminInfo implements Parcelable {
static final String TAG = "DeviceAdminInfo";
-
+
/**
* A type of policy that this device admin can use: limit the passwords
* that the user can select, via {@link DevicePolicyManager#setPasswordQuality}
* and {@link DevicePolicyManager#setPasswordMinimumLength}.
- *
+ *
* <p>To control this policy, the device admin must have a "limit-password"
* tag in the "uses-policies" section of its meta-data.
*/
public static final int USES_POLICY_LIMIT_PASSWORD = 0;
-
+
/**
* A type of policy that this device admin can use: able to watch login
* attempts from the user, via {@link DeviceAdminReceiver#ACTION_PASSWORD_FAILED},
* {@link DeviceAdminReceiver#ACTION_PASSWORD_SUCCEEDED}, and
* {@link DevicePolicyManager#getCurrentFailedPasswordAttempts}.
- *
+ *
* <p>To control this policy, the device admin must have a "watch-login"
* tag in the "uses-policies" section of its meta-data.
*/
@@ -76,7 +76,7 @@ public final class DeviceAdminInfo implements Parcelable {
* A type of policy that this device admin can use: able to reset the
* user's password via
* {@link DevicePolicyManager#resetPassword}.
- *
+ *
* <p>To control this policy, the device admin must have a "reset-password"
* tag in the "uses-policies" section of its meta-data.
*/
@@ -87,7 +87,7 @@ public final class DeviceAdminInfo implements Parcelable {
* to lock via{@link DevicePolicyManager#lockNow} or limit the
* maximum lock timeout for the device via
* {@link DevicePolicyManager#setMaximumTimeToLock}.
- *
+ *
* <p>To control this policy, the device admin must have a "force-lock"
* tag in the "uses-policies" section of its meta-data.
*/
@@ -97,7 +97,7 @@ public final class DeviceAdminInfo implements Parcelable {
* A type of policy that this device admin can use: able to factory
* reset the device, erasing all of the user's data, via
* {@link DevicePolicyManager#wipeData}.
- *
+ *
* <p>To control this policy, the device admin must have a "wipe-data"
* tag in the "uses-policies" section of its meta-data.
*/
@@ -138,13 +138,21 @@ public final class DeviceAdminInfo implements Parcelable {
*/
public static final int USES_POLICY_DISABLE_CAMERA = 8;
+ /**
+ * A type of policy that this device admin can use: disables use of keyguard widgets.
+ *
+ * <p>To control this policy, the device admin must have a "disable-keyguard-widgets"
+ * tag in the "uses-policies" section of its meta-data.
+ */
+ public static final int USES_POLICY_DISABLE_KEYGUARD_FEATURES = 9;
+
/** @hide */
public static class PolicyInfo {
public final int ident;
final public String tag;
final public int label;
final public int description;
-
+
public PolicyInfo(int identIn, String tagIn, int labelIn, int descriptionIn) {
ident = identIn;
tag = tagIn;
@@ -152,11 +160,11 @@ public final class DeviceAdminInfo implements Parcelable {
description = descriptionIn;
}
}
-
+
static ArrayList<PolicyInfo> sPoliciesDisplayOrder = new ArrayList<PolicyInfo>();
static HashMap<String, Integer> sKnownPolicies = new HashMap<String, Integer>();
static SparseArray<PolicyInfo> sRevKnownPolicies = new SparseArray<PolicyInfo>();
-
+
static {
sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_WIPE_DATA, "wipe-data",
com.android.internal.R.string.policylab_wipeData,
@@ -185,6 +193,10 @@ public final class DeviceAdminInfo implements Parcelable {
sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_DISABLE_CAMERA, "disable-camera",
com.android.internal.R.string.policylab_disableCamera,
com.android.internal.R.string.policydesc_disableCamera));
+ sPoliciesDisplayOrder.add(new PolicyInfo(
+ USES_POLICY_DISABLE_KEYGUARD_FEATURES, "disable-keyguard-features",
+ com.android.internal.R.string.policylab_disableKeyguardFeatures,
+ com.android.internal.R.string.policydesc_disableKeyguardFeatures));
for (int i=0; i<sPoliciesDisplayOrder.size(); i++) {
PolicyInfo pi = sPoliciesDisplayOrder.get(i);
@@ -192,25 +204,25 @@ public final class DeviceAdminInfo implements Parcelable {
sKnownPolicies.put(pi.tag, pi.ident);
}
}
-
+
/**
* The BroadcastReceiver that implements this device admin component.
*/
final ResolveInfo mReceiver;
-
+
/**
* Whether this should be visible to the user.
*/
boolean mVisible;
-
+
/**
* The policies this administrator needs access to.
*/
int mUsesPolicies;
-
+
/**
* Constructor.
- *
+ *
* @param context The Context in which we are parsing the device admin.
* @param receiver The ResolveInfo returned from the package manager about
* this device admin's component.
@@ -219,9 +231,9 @@ public final class DeviceAdminInfo implements Parcelable {
throws XmlPullParserException, IOException {
mReceiver = receiver;
ActivityInfo ai = receiver.activityInfo;
-
+
PackageManager pm = context.getPackageManager();
-
+
XmlResourceParser parser = null;
try {
parser = ai.loadXmlMetaData(pm, DeviceAdminReceiver.DEVICE_ADMIN_META_DATA);
@@ -229,30 +241,30 @@ public final class DeviceAdminInfo implements Parcelable {
throw new XmlPullParserException("No "
+ DeviceAdminReceiver.DEVICE_ADMIN_META_DATA + " meta-data");
}
-
+
Resources res = pm.getResourcesForApplication(ai.applicationInfo);
-
+
AttributeSet attrs = Xml.asAttributeSet(parser);
-
+
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& type != XmlPullParser.START_TAG) {
}
-
+
String nodeName = parser.getName();
if (!"device-admin".equals(nodeName)) {
throw new XmlPullParserException(
"Meta-data does not start with device-admin tag");
}
-
+
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.DeviceAdmin);
mVisible = sa.getBoolean(
com.android.internal.R.styleable.DeviceAdmin_visible, true);
-
+
sa.recycle();
-
+
int outerDepth = parser.getDepth();
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -290,14 +302,14 @@ public final class DeviceAdminInfo implements Parcelable {
mReceiver = ResolveInfo.CREATOR.createFromParcel(source);
mUsesPolicies = source.readInt();
}
-
+
/**
* Return the .apk package that implements this device admin.
*/
public String getPackageName() {
return mReceiver.activityInfo.packageName;
}
-
+
/**
* Return the class name of the receiver component that implements
* this device admin.
@@ -321,20 +333,20 @@ public final class DeviceAdminInfo implements Parcelable {
return new ComponentName(mReceiver.activityInfo.packageName,
mReceiver.activityInfo.name);
}
-
+
/**
* Load the user-displayed label for this device admin.
- *
+ *
* @param pm Supply a PackageManager used to load the device admin's
* resources.
*/
public CharSequence loadLabel(PackageManager pm) {
return mReceiver.loadLabel(pm);
}
-
+
/**
* Load user-visible description associated with this device admin.
- *
+ *
* @param pm Supply a PackageManager used to load the device admin's
* resources.
*/
@@ -351,17 +363,17 @@ public final class DeviceAdminInfo implements Parcelable {
}
throw new NotFoundException();
}
-
+
/**
* Load the user-displayed icon for this device admin.
- *
+ *
* @param pm Supply a PackageManager used to load the device admin's
* resources.
*/
public Drawable loadIcon(PackageManager pm) {
return mReceiver.loadIcon(pm);
}
-
+
/**
* Returns whether this device admin would like to be visible to the
* user, even when it is not enabled.
@@ -369,7 +381,7 @@ public final class DeviceAdminInfo implements Parcelable {
public boolean isVisible() {
return mVisible;
}
-
+
/**
* Return true if the device admin has requested that it be able to use
* the given policy control. The possible policy identifier inputs are:
@@ -382,7 +394,7 @@ public final class DeviceAdminInfo implements Parcelable {
public boolean usesPolicy(int policyIdent) {
return (mUsesPolicies & (1<<policyIdent)) != 0;
}
-
+
/**
* Return the XML tag name for the given policy identifier. Valid identifiers
* are as per {@link #usesPolicy(int)}. If the given identifier is not
@@ -391,7 +403,7 @@ public final class DeviceAdminInfo implements Parcelable {
public String getTagForPolicy(int policyIdent) {
return sRevKnownPolicies.get(policyIdent).tag;
}
-
+
/** @hide */
public ArrayList<PolicyInfo> getUsedPolicies() {
ArrayList<PolicyInfo> res = new ArrayList<PolicyInfo>();
@@ -403,25 +415,25 @@ public final class DeviceAdminInfo implements Parcelable {
}
return res;
}
-
+
/** @hide */
public void writePoliciesToXml(XmlSerializer out)
throws IllegalArgumentException, IllegalStateException, IOException {
out.attribute(null, "flags", Integer.toString(mUsesPolicies));
}
-
+
/** @hide */
public void readPoliciesFromXml(XmlPullParser parser)
throws XmlPullParserException, IOException {
mUsesPolicies = Integer.parseInt(
parser.getAttributeValue(null, "flags"));
}
-
+
public void dump(Printer pw, String prefix) {
pw.println(prefix + "Receiver:");
mReceiver.dump(pw, prefix + " ");
}
-
+
@Override
public String toString() {
return "DeviceAdminInfo{" + mReceiver.activityInfo.name + "}";
@@ -429,7 +441,7 @@ public final class DeviceAdminInfo implements Parcelable {
/**
* Used to package this object into a {@link Parcel}.
- *
+ *
* @param dest The {@link Parcel} to be written.
* @param flags The flags used for parceling.
*/
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0b58396..6966793 100755
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -29,6 +29,7 @@ import android.os.Handler;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.util.Log;
import java.io.IOException;
@@ -131,7 +132,7 @@ public class DevicePolicyManager {
public boolean isAdminActive(ComponentName who) {
if (mService != null) {
try {
- return mService.isAdminActive(who);
+ return mService.isAdminActive(who, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -147,7 +148,7 @@ public class DevicePolicyManager {
public List<ComponentName> getActiveAdmins() {
if (mService != null) {
try {
- return mService.getActiveAdmins();
+ return mService.getActiveAdmins(UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -156,12 +157,14 @@ public class DevicePolicyManager {
}
/**
+ * Used by package administration code to determine if a package can be stopped
+ * or uninstalled.
* @hide
*/
public boolean packageHasActiveAdmins(String packageName) {
if (mService != null) {
try {
- return mService.packageHasActiveAdmins(packageName);
+ return mService.packageHasActiveAdmins(packageName, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -178,7 +181,7 @@ public class DevicePolicyManager {
public void removeActiveAdmin(ComponentName who) {
if (mService != null) {
try {
- mService.removeActiveAdmin(who);
+ mService.removeActiveAdmin(who, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -197,7 +200,7 @@ public class DevicePolicyManager {
public boolean hasGrantedPolicy(ComponentName admin, int usesPolicy) {
if (mService != null) {
try {
- return mService.hasGrantedPolicy(admin, usesPolicy);
+ return mService.hasGrantedPolicy(admin, usesPolicy, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -289,7 +292,7 @@ public class DevicePolicyManager {
public void setPasswordQuality(ComponentName admin, int quality) {
if (mService != null) {
try {
- mService.setPasswordQuality(admin, quality);
+ mService.setPasswordQuality(admin, quality, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -303,9 +306,14 @@ public class DevicePolicyManager {
* all admins.
*/
public int getPasswordQuality(ComponentName admin) {
+ return getPasswordQuality(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public int getPasswordQuality(ComponentName admin, int userHandle) {
if (mService != null) {
try {
- return mService.getPasswordQuality(admin);
+ return mService.getPasswordQuality(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -337,7 +345,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumLength(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumLength(admin, length);
+ mService.setPasswordMinimumLength(admin, length, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -351,9 +359,14 @@ public class DevicePolicyManager {
* all admins.
*/
public int getPasswordMinimumLength(ComponentName admin) {
+ return getPasswordMinimumLength(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public int getPasswordMinimumLength(ComponentName admin, int userHandle) {
if (mService != null) {
try {
- return mService.getPasswordMinimumLength(admin);
+ return mService.getPasswordMinimumLength(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -386,7 +399,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumUpperCase(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumUpperCase(admin, length);
+ mService.setPasswordMinimumUpperCase(admin, length, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -406,9 +419,14 @@ public class DevicePolicyManager {
* password.
*/
public int getPasswordMinimumUpperCase(ComponentName admin) {
+ return getPasswordMinimumUpperCase(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public int getPasswordMinimumUpperCase(ComponentName admin, int userHandle) {
if (mService != null) {
try {
- return mService.getPasswordMinimumUpperCase(admin);
+ return mService.getPasswordMinimumUpperCase(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -441,7 +459,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumLowerCase(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumLowerCase(admin, length);
+ mService.setPasswordMinimumLowerCase(admin, length, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -461,9 +479,14 @@ public class DevicePolicyManager {
* password.
*/
public int getPasswordMinimumLowerCase(ComponentName admin) {
+ return getPasswordMinimumLowerCase(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public int getPasswordMinimumLowerCase(ComponentName admin, int userHandle) {
if (mService != null) {
try {
- return mService.getPasswordMinimumLowerCase(admin);
+ return mService.getPasswordMinimumLowerCase(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -495,7 +518,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumLetters(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumLetters(admin, length);
+ mService.setPasswordMinimumLetters(admin, length, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -514,9 +537,14 @@ public class DevicePolicyManager {
* @return The minimum number of letters required in the password.
*/
public int getPasswordMinimumLetters(ComponentName admin) {
+ return getPasswordMinimumLetters(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public int getPasswordMinimumLetters(ComponentName admin, int userHandle) {
if (mService != null) {
try {
- return mService.getPasswordMinimumLetters(admin);
+ return mService.getPasswordMinimumLetters(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -548,7 +576,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumNumeric(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumNumeric(admin, length);
+ mService.setPasswordMinimumNumeric(admin, length, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -567,9 +595,14 @@ public class DevicePolicyManager {
* @return The minimum number of numerical digits required in the password.
*/
public int getPasswordMinimumNumeric(ComponentName admin) {
+ return getPasswordMinimumNumeric(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public int getPasswordMinimumNumeric(ComponentName admin, int userHandle) {
if (mService != null) {
try {
- return mService.getPasswordMinimumNumeric(admin);
+ return mService.getPasswordMinimumNumeric(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -601,7 +634,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumSymbols(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumSymbols(admin, length);
+ mService.setPasswordMinimumSymbols(admin, length, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -620,9 +653,14 @@ public class DevicePolicyManager {
* @return The minimum number of symbols required in the password.
*/
public int getPasswordMinimumSymbols(ComponentName admin) {
+ return getPasswordMinimumSymbols(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public int getPasswordMinimumSymbols(ComponentName admin, int userHandle) {
if (mService != null) {
try {
- return mService.getPasswordMinimumSymbols(admin);
+ return mService.getPasswordMinimumSymbols(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -654,7 +692,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumNonLetter(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumNonLetter(admin, length);
+ mService.setPasswordMinimumNonLetter(admin, length, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -673,9 +711,14 @@ public class DevicePolicyManager {
* @return The minimum number of letters required in the password.
*/
public int getPasswordMinimumNonLetter(ComponentName admin) {
+ return getPasswordMinimumNonLetter(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public int getPasswordMinimumNonLetter(ComponentName admin, int userHandle) {
if (mService != null) {
try {
- return mService.getPasswordMinimumNonLetter(admin);
+ return mService.getPasswordMinimumNonLetter(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -708,7 +751,7 @@ public class DevicePolicyManager {
public void setPasswordHistoryLength(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordHistoryLength(admin, length);
+ mService.setPasswordHistoryLength(admin, length, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -737,7 +780,7 @@ public class DevicePolicyManager {
public void setPasswordExpirationTimeout(ComponentName admin, long timeout) {
if (mService != null) {
try {
- mService.setPasswordExpirationTimeout(admin, timeout);
+ mService.setPasswordExpirationTimeout(admin, timeout, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -756,7 +799,7 @@ public class DevicePolicyManager {
public long getPasswordExpirationTimeout(ComponentName admin) {
if (mService != null) {
try {
- return mService.getPasswordExpirationTimeout(admin);
+ return mService.getPasswordExpirationTimeout(admin, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -776,7 +819,7 @@ public class DevicePolicyManager {
public long getPasswordExpiration(ComponentName admin) {
if (mService != null) {
try {
- return mService.getPasswordExpiration(admin);
+ return mService.getPasswordExpiration(admin, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -792,9 +835,14 @@ public class DevicePolicyManager {
* @return The length of the password history
*/
public int getPasswordHistoryLength(ComponentName admin) {
+ return getPasswordHistoryLength(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public int getPasswordHistoryLength(ComponentName admin, int userHandle) {
if (mService != null) {
try {
- return mService.getPasswordHistoryLength(admin);
+ return mService.getPasswordHistoryLength(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -828,7 +876,7 @@ public class DevicePolicyManager {
public boolean isActivePasswordSufficient() {
if (mService != null) {
try {
- return mService.isActivePasswordSufficient();
+ return mService.isActivePasswordSufficient(UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -847,7 +895,7 @@ public class DevicePolicyManager {
public int getCurrentFailedPasswordAttempts() {
if (mService != null) {
try {
- return mService.getCurrentFailedPasswordAttempts();
+ return mService.getCurrentFailedPasswordAttempts(UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -877,7 +925,7 @@ public class DevicePolicyManager {
public void setMaximumFailedPasswordsForWipe(ComponentName admin, int num) {
if (mService != null) {
try {
- mService.setMaximumFailedPasswordsForWipe(admin, num);
+ mService.setMaximumFailedPasswordsForWipe(admin, num, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -892,9 +940,14 @@ public class DevicePolicyManager {
* all admins.
*/
public int getMaximumFailedPasswordsForWipe(ComponentName admin) {
+ return getMaximumFailedPasswordsForWipe(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public int getMaximumFailedPasswordsForWipe(ComponentName admin, int userHandle) {
if (mService != null) {
try {
- return mService.getMaximumFailedPasswordsForWipe(admin);
+ return mService.getMaximumFailedPasswordsForWipe(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -933,7 +986,7 @@ public class DevicePolicyManager {
public boolean resetPassword(String password, int flags) {
if (mService != null) {
try {
- return mService.resetPassword(password, flags);
+ return mService.resetPassword(password, flags, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -957,7 +1010,7 @@ public class DevicePolicyManager {
public void setMaximumTimeToLock(ComponentName admin, long timeMs) {
if (mService != null) {
try {
- mService.setMaximumTimeToLock(admin, timeMs);
+ mService.setMaximumTimeToLock(admin, timeMs, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -971,9 +1024,14 @@ public class DevicePolicyManager {
* all admins.
*/
public long getMaximumTimeToLock(ComponentName admin) {
+ return getMaximumTimeToLock(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public long getMaximumTimeToLock(ComponentName admin, int userHandle) {
if (mService != null) {
try {
- return mService.getMaximumTimeToLock(admin);
+ return mService.getMaximumTimeToLock(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1021,7 +1079,7 @@ public class DevicePolicyManager {
public void wipeData(int flags) {
if (mService != null) {
try {
- mService.wipeData(flags);
+ mService.wipeData(flags, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1090,7 +1148,7 @@ public class DevicePolicyManager {
}
android.net.Proxy.validate(hostName, Integer.toString(port), exclSpec);
}
- return mService.setGlobalProxy(admin, hostSpec, exclSpec);
+ return mService.setGlobalProxy(admin, hostSpec, exclSpec, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1107,7 +1165,7 @@ public class DevicePolicyManager {
public ComponentName getGlobalProxyAdmin() {
if (mService != null) {
try {
- return mService.getGlobalProxyAdmin();
+ return mService.getGlobalProxyAdmin(UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1155,6 +1213,26 @@ public class DevicePolicyManager {
= "android.app.action.START_ENCRYPTION";
/**
+ * Widgets are enabled in keyguard
+ */
+ public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0;
+
+ /**
+ * Disable all keyguard widgets
+ */
+ public static final int KEYGUARD_DISABLE_WIDGETS_ALL = 1 << 0;
+
+ /**
+ * Disable the camera on secure keyguard screens (e.g. PIN/Pattern/Password)
+ */
+ public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 1 << 1;
+
+ /**
+ * Disable all current and future keyguard customizations
+ */
+ public static final int KEYGUARD_DISABLE_FEATURES_ALL = 0x7fffffff;
+
+ /**
* Called by an application that is administering the device to
* request that the storage system be encrypted.
*
@@ -1189,7 +1267,7 @@ public class DevicePolicyManager {
public int setStorageEncryption(ComponentName admin, boolean encrypt) {
if (mService != null) {
try {
- return mService.setStorageEncryption(admin, encrypt);
+ return mService.setStorageEncryption(admin, encrypt, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1209,7 +1287,7 @@ public class DevicePolicyManager {
public boolean getStorageEncryption(ComponentName admin) {
if (mService != null) {
try {
- return mService.getStorageEncryption(admin);
+ return mService.getStorageEncryption(admin, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1234,9 +1312,14 @@ public class DevicePolicyManager {
* {@link #ENCRYPTION_STATUS_ACTIVATING}, or{@link #ENCRYPTION_STATUS_ACTIVE}.
*/
public int getStorageEncryptionStatus() {
+ return getStorageEncryptionStatus(UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public int getStorageEncryptionStatus(int userHandle) {
if (mService != null) {
try {
- return mService.getStorageEncryptionStatus();
+ return mService.getStorageEncryptionStatus(userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1259,7 +1342,7 @@ public class DevicePolicyManager {
public void setCameraDisabled(ComponentName admin, boolean disabled) {
if (mService != null) {
try {
- mService.setCameraDisabled(admin, disabled);
+ mService.setCameraDisabled(admin, disabled, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1273,9 +1356,14 @@ public class DevicePolicyManager {
* have disabled the camera
*/
public boolean getCameraDisabled(ComponentName admin) {
+ return getCameraDisabled(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public boolean getCameraDisabled(ComponentName admin, int userHandle) {
if (mService != null) {
try {
- return mService.getCameraDisabled(admin);
+ return mService.getCameraDisabled(admin, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1284,12 +1372,57 @@ public class DevicePolicyManager {
}
/**
+ * Called by an application that is administering the device to disable keyguard customizations,
+ * such as widgets. After setting this, keyguard features will be disabled according to the
+ * provided feature list.
+ *
+ * <p>The calling device admin must have requested
+ * {@link DeviceAdminInfo#USES_POLICY_DISABLE_KEYGUARD_FEATURES} to be able to call
+ * this method; if it has not, a security exception will be thrown.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param which {@link DevicePolicyManager#KEYGUARD_DISABLE_WIDGETS_ALL} or
+ * {@link DevicePolicyManager#KEYGUARD_DISABLE_FEATURES_NONE} (the default).
+ */
+ public void setKeyguardDisabledFeatures(ComponentName admin, int which) {
+ if (mService != null) {
+ try {
+ mService.setKeyguardDisabledFeatures(admin, which, UserHandle.myUserId());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Determine whether or not features have been disabled in keyguard either by the current
+ * admin, if specified, or all admins.
+ * @param admin The name of the admin component to check, or null to check if any admins
+ * have disabled features in keyguard.
+ */
+ public int getKeyguardDisabledFeatures(ComponentName admin) {
+ return getKeyguardDisabledFeatures(admin, UserHandle.myUserId());
+ }
+
+ /** @hide per-user version */
+ public int getKeyguardDisabledFeatures(ComponentName admin, int userHandle) {
+ if (mService != null) {
+ try {
+ return mService.getKeyguardDisabledFeatures(admin, userHandle);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return KEYGUARD_DISABLE_FEATURES_NONE;
+ }
+
+ /**
* @hide
*/
public void setActiveAdmin(ComponentName policyReceiver, boolean refreshing) {
if (mService != null) {
try {
- mService.setActiveAdmin(policyReceiver, refreshing);
+ mService.setActiveAdmin(policyReceiver, refreshing, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1330,7 +1463,7 @@ public class DevicePolicyManager {
public void getRemoveWarning(ComponentName admin, RemoteCallback result) {
if (mService != null) {
try {
- mService.getRemoveWarning(admin, result);
+ mService.getRemoveWarning(admin, result, UserHandle.myUserId());
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1341,11 +1474,11 @@ public class DevicePolicyManager {
* @hide
*/
public void setActivePasswordState(int quality, int length, int letters, int uppercase,
- int lowercase, int numbers, int symbols, int nonletter) {
+ int lowercase, int numbers, int symbols, int nonletter, int userHandle) {
if (mService != null) {
try {
mService.setActivePasswordState(quality, length, letters, uppercase, lowercase,
- numbers, symbols, nonletter);
+ numbers, symbols, nonletter, userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1355,10 +1488,10 @@ public class DevicePolicyManager {
/**
* @hide
*/
- public void reportFailedPasswordAttempt() {
+ public void reportFailedPasswordAttempt(int userHandle) {
if (mService != null) {
try {
- mService.reportFailedPasswordAttempt();
+ mService.reportFailedPasswordAttempt(userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1368,14 +1501,13 @@ public class DevicePolicyManager {
/**
* @hide
*/
- public void reportSuccessfulPasswordAttempt() {
+ public void reportSuccessfulPasswordAttempt(int userHandle) {
if (mService != null) {
try {
- mService.reportSuccessfulPasswordAttempt();
+ mService.reportSuccessfulPasswordAttempt(userHandle);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
}
}
-
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9419a62..e061ab3 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -25,73 +25,76 @@ import android.os.RemoteCallback;
* {@hide}
*/
interface IDevicePolicyManager {
- void setPasswordQuality(in ComponentName who, int quality);
- int getPasswordQuality(in ComponentName who);
+ void setPasswordQuality(in ComponentName who, int quality, int userHandle);
+ int getPasswordQuality(in ComponentName who, int userHandle);
- void setPasswordMinimumLength(in ComponentName who, int length);
- int getPasswordMinimumLength(in ComponentName who);
+ void setPasswordMinimumLength(in ComponentName who, int length, int userHandle);
+ int getPasswordMinimumLength(in ComponentName who, int userHandle);
- void setPasswordMinimumUpperCase(in ComponentName who, int length);
- int getPasswordMinimumUpperCase(in ComponentName who);
+ void setPasswordMinimumUpperCase(in ComponentName who, int length, int userHandle);
+ int getPasswordMinimumUpperCase(in ComponentName who, int userHandle);
- void setPasswordMinimumLowerCase(in ComponentName who, int length);
- int getPasswordMinimumLowerCase(in ComponentName who);
+ void setPasswordMinimumLowerCase(in ComponentName who, int length, int userHandle);
+ int getPasswordMinimumLowerCase(in ComponentName who, int userHandle);
- void setPasswordMinimumLetters(in ComponentName who, int length);
- int getPasswordMinimumLetters(in ComponentName who);
+ void setPasswordMinimumLetters(in ComponentName who, int length, int userHandle);
+ int getPasswordMinimumLetters(in ComponentName who, int userHandle);
- void setPasswordMinimumNumeric(in ComponentName who, int length);
- int getPasswordMinimumNumeric(in ComponentName who);
+ void setPasswordMinimumNumeric(in ComponentName who, int length, int userHandle);
+ int getPasswordMinimumNumeric(in ComponentName who, int userHandle);
- void setPasswordMinimumSymbols(in ComponentName who, int length);
- int getPasswordMinimumSymbols(in ComponentName who);
+ void setPasswordMinimumSymbols(in ComponentName who, int length, int userHandle);
+ int getPasswordMinimumSymbols(in ComponentName who, int userHandle);
- void setPasswordMinimumNonLetter(in ComponentName who, int length);
- int getPasswordMinimumNonLetter(in ComponentName who);
-
- void setPasswordHistoryLength(in ComponentName who, int length);
- int getPasswordHistoryLength(in ComponentName who);
+ void setPasswordMinimumNonLetter(in ComponentName who, int length, int userHandle);
+ int getPasswordMinimumNonLetter(in ComponentName who, int userHandle);
- void setPasswordExpirationTimeout(in ComponentName who, long expiration);
- long getPasswordExpirationTimeout(in ComponentName who);
+ void setPasswordHistoryLength(in ComponentName who, int length, int userHandle);
+ int getPasswordHistoryLength(in ComponentName who, int userHandle);
- long getPasswordExpiration(in ComponentName who);
+ void setPasswordExpirationTimeout(in ComponentName who, long expiration, int userHandle);
+ long getPasswordExpirationTimeout(in ComponentName who, int userHandle);
+
+ long getPasswordExpiration(in ComponentName who, int userHandle);
+
+ boolean isActivePasswordSufficient(int userHandle);
+ int getCurrentFailedPasswordAttempts(int userHandle);
+
+ void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num, int userHandle);
+ int getMaximumFailedPasswordsForWipe(in ComponentName admin, int userHandle);
+
+ boolean resetPassword(String password, int flags, int userHandle);
+
+ void setMaximumTimeToLock(in ComponentName who, long timeMs, int userHandle);
+ long getMaximumTimeToLock(in ComponentName who, int userHandle);
- boolean isActivePasswordSufficient();
- int getCurrentFailedPasswordAttempts();
-
- void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num);
- int getMaximumFailedPasswordsForWipe(in ComponentName admin);
-
- boolean resetPassword(String password, int flags);
-
- void setMaximumTimeToLock(in ComponentName who, long timeMs);
- long getMaximumTimeToLock(in ComponentName who);
-
void lockNow();
-
- void wipeData(int flags);
-
- ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
- ComponentName getGlobalProxyAdmin();
-
- int setStorageEncryption(in ComponentName who, boolean encrypt);
- boolean getStorageEncryption(in ComponentName who);
- int getStorageEncryptionStatus();
-
- void setCameraDisabled(in ComponentName who, boolean disabled);
- boolean getCameraDisabled(in ComponentName who);
-
- void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing);
- boolean isAdminActive(in ComponentName policyReceiver);
- List<ComponentName> getActiveAdmins();
- boolean packageHasActiveAdmins(String packageName);
- void getRemoveWarning(in ComponentName policyReceiver, in RemoteCallback result);
- void removeActiveAdmin(in ComponentName policyReceiver);
- boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy);
-
+
+ void wipeData(int flags, int userHandle);
+
+ ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList, int userHandle);
+ ComponentName getGlobalProxyAdmin(int userHandle);
+
+ int setStorageEncryption(in ComponentName who, boolean encrypt, int userHandle);
+ boolean getStorageEncryption(in ComponentName who, int userHandle);
+ int getStorageEncryptionStatus(int userHandle);
+
+ void setCameraDisabled(in ComponentName who, boolean disabled, int userHandle);
+ boolean getCameraDisabled(in ComponentName who, int userHandle);
+
+ void setKeyguardDisabledFeatures(in ComponentName who, int which, int userHandle);
+ int getKeyguardDisabledFeatures(in ComponentName who, int userHandle);
+
+ void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing, int userHandle);
+ boolean isAdminActive(in ComponentName policyReceiver, int userHandle);
+ List<ComponentName> getActiveAdmins(int userHandle);
+ boolean packageHasActiveAdmins(String packageName, int userHandle);
+ void getRemoveWarning(in ComponentName policyReceiver, in RemoteCallback result, int userHandle);
+ void removeActiveAdmin(in ComponentName policyReceiver, int userHandle);
+ boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle);
+
void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase,
- int numbers, int symbols, int nonletter);
- void reportFailedPasswordAttempt();
- void reportSuccessfulPasswordAttempt();
+ int numbers, int symbols, int nonletter, int userHandle);
+ void reportFailedPasswordAttempt(int userHandle);
+ void reportSuccessfulPasswordAttempt(int userHandle);
}
diff --git a/core/java/android/app/backup/WallpaperBackupHelper.java b/core/java/android/app/backup/WallpaperBackupHelper.java
index a74a268..9e8ab2c 100644
--- a/core/java/android/app/backup/WallpaperBackupHelper.java
+++ b/core/java/android/app/backup/WallpaperBackupHelper.java
@@ -20,7 +20,9 @@ import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Point;
+import android.os.Environment;
import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
import android.util.Slog;
import android.view.Display;
import android.view.WindowManager;
@@ -39,8 +41,12 @@ public class WallpaperBackupHelper extends FileBackupHelperBase implements Backu
// This path must match what the WallpaperManagerService uses
// TODO: Will need to change if backing up non-primary user's wallpaper
- public static final String WALLPAPER_IMAGE = "/data/system/users/0/wallpaper";
- public static final String WALLPAPER_INFO = "/data/system/users/0/wallpaper_info.xml";
+ public static final String WALLPAPER_IMAGE =
+ new File(Environment.getUserSystemDirectory(UserHandle.USER_OWNER),
+ "wallpaper").getAbsolutePath();
+ public static final String WALLPAPER_INFO =
+ new File(Environment.getUserSystemDirectory(UserHandle.USER_OWNER),
+ "wallpaper_info.xml").getAbsolutePath();
// Use old keys to keep legacy data compatibility and avoid writing two wallpapers
public static final String WALLPAPER_IMAGE_KEY =
"/data/data/com.android.settings/files/wallpaper";
@@ -50,7 +56,9 @@ public class WallpaperBackupHelper extends FileBackupHelperBase implements Backu
// will be saved to this file from the restore stream, then renamed to the proper
// location if it's deemed suitable.
// TODO: Will need to change if backing up non-primary user's wallpaper
- private static final String STAGE_FILE = "/data/system/users/0/wallpaper-tmp";
+ private static final String STAGE_FILE =
+ new File(Environment.getUserSystemDirectory(UserHandle.USER_OWNER),
+ "wallpaper-tmp").getAbsolutePath();
Context mContext;
String[] mFiles;
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 08bc0ac..cb61a71 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -19,16 +19,21 @@ package android.appwidget;
import java.util.ArrayList;
import java.util.HashMap;
+import android.app.ActivityThread;
import android.content.Context;
+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.UserHandle;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.widget.RemoteViews;
+import android.widget.RemoteViews.OnClickHandler;
import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
@@ -41,7 +46,8 @@ public class AppWidgetHost {
static final int HANDLE_UPDATE = 1;
static final int HANDLE_PROVIDER_CHANGED = 2;
- static final int HANDLE_VIEW_DATA_CHANGED = 3;
+ static final int HANDLE_PROVIDERS_CHANGED = 3;
+ static final int HANDLE_VIEW_DATA_CHANGED = 4;
final static Object sServiceLock = new Object();
static IAppWidgetService sService;
@@ -52,6 +58,9 @@ public class AppWidgetHost {
class Callbacks extends IAppWidgetHost.Stub {
public void updateAppWidget(int appWidgetId, RemoteViews views) {
+ if (isLocalBinder() && views != null) {
+ views = views.clone();
+ }
Message msg = mHandler.obtainMessage(HANDLE_UPDATE);
msg.arg1 = appWidgetId;
msg.obj = views;
@@ -59,12 +68,20 @@ public class AppWidgetHost {
}
public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
+ if (isLocalBinder() && info != null) {
+ info = info.clone();
+ }
Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED);
msg.arg1 = appWidgetId;
msg.obj = info;
msg.sendToTarget();
}
+ public void providersChanged() {
+ Message msg = mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED);
+ msg.sendToTarget();
+ }
+
public void viewDataChanged(int appWidgetId, int viewId) {
Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED);
msg.arg1 = appWidgetId;
@@ -77,7 +94,7 @@ public class AppWidgetHost {
public UpdateHandler(Looper looper) {
super(looper);
}
-
+
public void handleMessage(Message msg) {
switch (msg.what) {
case HANDLE_UPDATE: {
@@ -88,6 +105,10 @@ public class AppWidgetHost {
onProviderChanged(msg.arg1, (AppWidgetProviderInfo)msg.obj);
break;
}
+ case HANDLE_PROVIDERS_CHANGED: {
+ onProvidersChanged();
+ break;
+ }
case HANDLE_VIEW_DATA_CHANGED: {
viewDataChanged(msg.arg1, msg.arg2);
break;
@@ -95,18 +116,31 @@ public class AppWidgetHost {
}
}
}
-
+
Handler mHandler;
int mHostId;
Callbacks mCallbacks = new Callbacks();
final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>();
+ private OnClickHandler mOnClickHandler;
public AppWidgetHost(Context context, int hostId) {
+ this(context, hostId, null, context.getMainLooper());
+ }
+
+ /**
+ * @hide
+ */
+ public AppWidgetHost(Context context, int hostId, OnClickHandler handler, Looper looper) {
mContext = context;
mHostId = hostId;
- mHandler = new UpdateHandler(context.getMainLooper());
+ mOnClickHandler = handler;
+ mHandler = new UpdateHandler(looper);
mDisplayMetrics = context.getResources().getDisplayMetrics();
+ bindService();
+ }
+
+ private static void bindService() {
synchronized (sServiceLock) {
if (sService == null) {
IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
@@ -122,7 +156,7 @@ public class AppWidgetHost {
public void startListening() {
int[] updatedIds;
ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
-
+
try {
if (mPackageName == null) {
mPackageName = mContext.getPackageName();
@@ -170,7 +204,40 @@ public class AppWidgetHost {
}
/**
- * Stop listening to changes for this AppWidget.
+ * Get a appWidgetId for a host in the calling process.
+ *
+ * @return a appWidgetId
+ * @hide
+ */
+ public static int allocateAppWidgetIdForSystem(int hostId) {
+ checkCallerIsSystem();
+ try {
+ if (sService == null) {
+ bindService();
+ }
+ Context systemContext =
+ (Context) ActivityThread.currentActivityThread().getSystemContext();
+ String packageName = systemContext.getPackageName();
+ return sService.allocateAppWidgetId(packageName, hostId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ private static void checkCallerIsSystem() {
+ int uid = Process.myUid();
+ if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) {
+ return;
+ }
+ throw new SecurityException("Disallowed call for uid " + uid);
+ }
+
+ private boolean isLocalBinder() {
+ return Process.myPid() == Binder.getCallingPid();
+ }
+
+ /**
+ * Stop listening to changes for this AppWidget.
*/
public void deleteAppWidgetId(int appWidgetId) {
synchronized (mViews) {
@@ -185,6 +252,22 @@ public class AppWidgetHost {
}
/**
+ * Stop listening to changes for this AppWidget.
+ * @hide
+ */
+ public static void deleteAppWidgetIdForSystem(int appWidgetId) {
+ checkCallerIsSystem();
+ try {
+ if (sService == null) {
+ bindService();
+ }
+ sService.deleteAppWidgetId(appWidgetId);
+ } catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
* Remove all records about this host from the AppWidget manager.
* <ul>
* <li>Call this when initializing your database, as it might be because of a data wipe.</li>
@@ -225,6 +308,7 @@ public class AppWidgetHost {
public final AppWidgetHostView createView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
+ view.setOnClickHandler(mOnClickHandler);
view.setAppWidget(appWidgetId, appWidget);
synchronized (mViews) {
mViews.put(appWidgetId, view);
@@ -236,6 +320,7 @@ public class AppWidgetHost {
throw new RuntimeException("system server dead?", e);
}
view.updateAppWidget(views);
+
return view;
}
@@ -245,7 +330,7 @@ public class AppWidgetHost {
*/
protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
- return new AppWidgetHostView(context);
+ return new AppWidgetHostView(context, mOnClickHandler);
}
/**
@@ -255,7 +340,7 @@ public class AppWidgetHost {
AppWidgetHostView v;
// Convert complex to dp -- we are getting the AppWidgetProviderInfo from the
- // AppWidgetService, which doesn't have our context, hence we need to do the
+ // AppWidgetService, which doesn't have our context, hence we need to do the
// conversion here.
appWidget.minWidth =
TypedValue.complexToDimensionPixelSize(appWidget.minWidth, mDisplayMetrics);
@@ -274,6 +359,14 @@ public class AppWidgetHost {
}
}
+ /**
+ * Called when the set of available widgets changes (ie. widget containing packages
+ * are added, updated or removed, or widget components are enabled or disabled.)
+ */
+ protected void onProvidersChanged() {
+ // Do nothing
+ }
+
void updateAppWidgetView(int appWidgetId, RemoteViews views) {
AppWidgetHostView v;
synchronized (mViews) {
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index ed95ae5..f258f17 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -44,6 +44,7 @@ import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.RemoteViews;
+import android.widget.RemoteViews.OnClickHandler;
import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback;
import android.widget.TextView;
@@ -83,7 +84,8 @@ public class AppWidgetHostView extends FrameLayout {
long mFadeStartTime = -1;
Bitmap mOld;
Paint mOldPaint = new Paint();
-
+ private OnClickHandler mOnClickHandler;
+
/**
* Create a host view. Uses default fade animations.
*/
@@ -92,9 +94,17 @@ public class AppWidgetHostView extends FrameLayout {
}
/**
+ * @hide
+ */
+ public AppWidgetHostView(Context context, OnClickHandler handler) {
+ this(context, android.R.anim.fade_in, android.R.anim.fade_out);
+ mOnClickHandler = handler;
+ }
+
+ /**
* Create a host view. Uses specified animations when pushing
* {@link #updateAppWidget(RemoteViews)}.
- *
+ *
* @param animationIn Resource ID of in animation to use
* @param animationOut Resource ID of out animation to use
*/
@@ -109,6 +119,17 @@ public class AppWidgetHostView extends FrameLayout {
}
/**
+ * Pass the given handler to RemoteViews when updating this widget. Unless this
+ * is done immediatly after construction, a call to {@link #updateAppWidget(RemoteViews)}
+ * should be made.
+ * @param handler
+ * @hide
+ */
+ public void setOnClickHandler(OnClickHandler handler) {
+ mOnClickHandler = handler;
+ }
+
+ /**
* Set the AppWidget that will be displayed by this view. This method also adds default padding
* to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)}
* and can be overridden in order to add custom padding.
@@ -177,7 +198,7 @@ public class AppWidgetHostView extends FrameLayout {
public int getAppWidgetId() {
return mAppWidgetId;
}
-
+
public AppWidgetProviderInfo getAppWidgetInfo() {
return mInfo;
}
@@ -205,7 +226,12 @@ public class AppWidgetHostView extends FrameLayout {
if (jail == null) jail = new ParcelableSparseArray();
- super.dispatchRestoreInstanceState(jail);
+ try {
+ super.dispatchRestoreInstanceState(jail);
+ } catch (Exception e) {
+ Log.e(TAG, "failed to restoreInstanceState for widget id: " + mAppWidgetId + ", "
+ + (mInfo == null ? "null" : mInfo.provider), e);
+ }
}
/**
@@ -281,12 +307,13 @@ public class AppWidgetHostView extends FrameLayout {
* AppWidget provider. Will animate into these new views as needed
*/
public void updateAppWidget(RemoteViews remoteViews) {
+
if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);
boolean recycled = false;
View content = null;
Exception exception = null;
-
+
// Capture the old view into a bitmap so we can do the crossfade.
if (CROSSFADE) {
if (mFadeStartTime < 0) {
@@ -305,7 +332,7 @@ public class AppWidgetHostView extends FrameLayout {
}
}
}
-
+
if (remoteViews == null) {
if (mViewMode == VIEW_MODE_DEFAULT) {
// We've already done this -- nothing to do.
@@ -324,7 +351,7 @@ public class AppWidgetHostView extends FrameLayout {
// layout matches, try recycling it
if (content == null && layoutId == mLayoutId) {
try {
- remoteViews.reapply(mContext, mView);
+ remoteViews.reapply(mContext, mView, mOnClickHandler);
content = mView;
recycled = true;
if (LOGD) Log.d(TAG, "was able to recycled existing layout");
@@ -332,11 +359,11 @@ public class AppWidgetHostView extends FrameLayout {
exception = e;
}
}
-
+
// Try normal RemoteView inflation
if (content == null) {
try {
- content = remoteViews.apply(mContext, this);
+ content = remoteViews.apply(mContext, this, mOnClickHandler);
if (LOGD) Log.d(TAG, "had to inflate new layout");
} catch (RuntimeException e) {
exception = e;
@@ -346,7 +373,7 @@ public class AppWidgetHostView extends FrameLayout {
mLayoutId = layoutId;
mViewMode = VIEW_MODE_CONTENT;
}
-
+
if (content == null) {
if (mViewMode == VIEW_MODE_ERROR) {
// We've already done this -- nothing to do.
@@ -356,7 +383,7 @@ public class AppWidgetHostView extends FrameLayout {
content = getErrorView();
mViewMode = VIEW_MODE_ERROR;
}
-
+
if (!recycled) {
prepareView(content);
addView(content);
@@ -455,7 +482,7 @@ public class AppWidgetHostView extends FrameLayout {
return super.drawChild(canvas, child, drawingTime);
}
}
-
+
/**
* Prepare the given view to be shown. This might include adjusting
* {@link FrameLayout.LayoutParams} before inserting.
@@ -471,7 +498,7 @@ public class AppWidgetHostView extends FrameLayout {
requested.gravity = Gravity.CENTER;
view.setLayoutParams(requested);
}
-
+
/**
* Inflate and return the default layout requested by AppWidget provider.
*/
@@ -481,7 +508,7 @@ public class AppWidgetHostView extends FrameLayout {
}
View defaultView = null;
Exception exception = null;
-
+
try {
if (mInfo != null) {
Context theirContext = mContext.createPackageContext(
@@ -491,7 +518,17 @@ public class AppWidgetHostView extends FrameLayout {
theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater = inflater.cloneInContext(theirContext);
inflater.setFilter(sInflaterFilter);
- defaultView = inflater.inflate(mInfo.initialLayout, this, false);
+ AppWidgetManager manager = AppWidgetManager.getInstance(mContext);
+ Bundle options = manager.getAppWidgetOptions(mAppWidgetId);
+
+ int layoutId = mInfo.initialLayout;
+ if (options.containsKey(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)) {
+ int category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY);
+ if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) {
+ layoutId = mInfo.initialKeyguardLayout;
+ }
+ }
+ defaultView = inflater.inflate(layoutId, this, false);
} else {
Log.w(TAG, "can't inflate defaultView because mInfo is missing");
}
@@ -500,19 +537,19 @@ public class AppWidgetHostView extends FrameLayout {
} catch (RuntimeException e) {
exception = e;
}
-
+
if (exception != null) {
Log.w(TAG, "Error inflating AppWidget " + mInfo + ": " + exception.toString());
}
-
+
if (defaultView == null) {
if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error");
defaultView = getErrorView();
}
-
+
return defaultView;
}
-
+
/**
* Inflate and return a view that represents an error state.
*/
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 2df675e..888955c 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -150,26 +150,34 @@ public class AppWidgetManager {
public static final String EXTRA_APPWIDGET_ID = "appWidgetId";
/**
- * An bundle extra that contains the lower bound on the current width, in dips, of a widget instance.
+ * A bundle extra that contains the lower bound on the current width, in dips, of a widget instance.
*/
public static final String OPTION_APPWIDGET_MIN_WIDTH = "appWidgetMinWidth";
/**
- * An bundle extra that contains the lower bound on the current height, in dips, of a widget instance.
+ * A bundle extra that contains the lower bound on the current height, in dips, of a widget instance.
*/
public static final String OPTION_APPWIDGET_MIN_HEIGHT = "appWidgetMinHeight";
/**
- * An bundle extra that contains the upper bound on the current width, in dips, of a widget instance.
+ * A bundle extra that contains the upper bound on the current width, in dips, of a widget instance.
*/
public static final String OPTION_APPWIDGET_MAX_WIDTH = "appWidgetMaxWidth";
/**
- * An bundle extra that contains the upper bound on the current width, in dips, of a widget instance.
+ * A bundle extra that contains the upper bound on the current width, in dips, of a widget instance.
*/
public static final String OPTION_APPWIDGET_MAX_HEIGHT = "appWidgetMaxHeight";
/**
+ * A bundle extra that hints to the AppWidgetProvider the category of host that owns this
+ * this widget. Can have the value {@link
+ * AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN} or {@link
+ * AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD}.
+ */
+ public static final String OPTION_APPWIDGET_HOST_CATEGORY = "appWidgetCategory";
+
+ /**
* An intent extra which points to a bundle of extra information for a particular widget id.
* In particular this bundle can contain EXTRA_APPWIDGET_WIDTH and EXTRA_APPWIDGET_HEIGHT.
*/
@@ -208,6 +216,28 @@ public class AppWidgetManager {
public static final String EXTRA_CUSTOM_EXTRAS = "customExtras";
/**
+ * An intent extra to pass to the AppWidget picker which allows the picker to filter
+ * the list based on the {@link AppWidgetProviderInfo#widgetCategory}.
+ *
+ * @hide
+ */
+ public static final String EXTRA_CATEGORY_FILTER = "categoryFilter";
+
+ /**
+ * An intent extra to pass to the AppWidget picker which allows the picker to filter
+ * the list based on the {@link AppWidgetProviderInfo#widgetFeatures}.
+ * @hide
+ */
+ public static final String EXTRA_FEATURES_FILTER = "featuresFilter";
+
+ /**
+ * An intent extra to pass to the AppWidget picker to specify whether or not to sort
+ * the list of caller-specified extra AppWidgets along with the rest of the AppWidgets
+ * @hide
+ */
+ public static final String EXTRA_CUSTOM_SORT = "customSort";
+
+ /**
* A sentiel value that the AppWidget manager will never return as a appWidgetId.
*/
public static final int INVALID_APPWIDGET_ID = 0;
@@ -406,10 +436,9 @@ public class AppWidgetManager {
*
* This update differs from {@link #updateAppWidget(int[], RemoteViews)} in that the
* RemoteViews object which is passed is understood to be an incomplete representation of the
- * widget, and hence is not cached by the AppWidgetService. Note that because these updates are
- * not cached, any state that they modify that is not restored by restoreInstanceState will not
- * persist in the case that the widgets are restored using the cached version in
- * AppWidgetService.
+ * widget, and hence does not replace the cached representation of the widget. As of API
+ * level 17, the new properties set within the views objects will be appended to the cached
+ * representation of the widget, and hence will persist.
*
* Use with {@link RemoteViews#showNext(int)}, {@link RemoteViews#showPrevious(int)},
* {@link RemoteViews#setScrollPosition(int, int)} and similar commands.
@@ -568,7 +597,31 @@ public class AppWidgetManager {
*/
public void bindAppWidgetId(int appWidgetId, ComponentName provider) {
try {
- sService.bindAppWidgetId(appWidgetId, provider);
+ sService.bindAppWidgetId(appWidgetId, provider, null);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
+ * Set the component for a given appWidgetId.
+ *
+ * <p class="note">You need the BIND_APPWIDGET permission or the user must have enabled binding
+ * widgets always for your component. This method is used by the AppWidget picker and
+ * should not be used by other apps.
+ *
+ * @param appWidgetId The AppWidget instance for which to set the RemoteViews.
+ * @param provider The {@link android.content.BroadcastReceiver} that will be the AppWidget
+ * provider for this AppWidget.
+ * @param options Bundle containing options for the AppWidget. See also
+ * {@link #updateAppWidgetOptions(int, Bundle)}
+ *
+ * @hide
+ */
+ public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) {
+ try {
+ sService.bindAppWidgetId(appWidgetId, provider, options);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -594,7 +647,37 @@ public class AppWidgetManager {
}
try {
return sService.bindAppWidgetIdIfAllowed(
- mContext.getPackageName(), appWidgetId, provider);
+ mContext.getPackageName(), appWidgetId, provider, null);
+ }
+ catch (RemoteException e) {
+ throw new RuntimeException("system server dead?", e);
+ }
+ }
+
+ /**
+ * Set the component for a given appWidgetId.
+ *
+ * <p class="note">You need the BIND_APPWIDGET permission or the user must have enabled binding
+ * widgets always for your component. Should be used by apps that host widgets; if this
+ * method returns false, call {@link #ACTION_APPWIDGET_BIND} to request permission to
+ * bind
+ *
+ * @param appWidgetId The AppWidget instance for which to set the RemoteViews.
+ * @param provider The {@link android.content.BroadcastReceiver} that will be the AppWidget
+ * provider for this AppWidget.
+ * @param options Bundle containing options for the AppWidget. See also
+ * {@link #updateAppWidgetOptions(int, Bundle)}
+ *
+ * @return true if this component has permission to bind the AppWidget
+ */
+ public boolean bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider,
+ Bundle options) {
+ if (mContext == null) {
+ return false;
+ }
+ try {
+ return sService.bindAppWidgetIdIfAllowed(
+ mContext.getPackageName(), appWidgetId, provider, options);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index c33681d..8b62931 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -44,6 +44,28 @@ public class AppWidgetProviderInfo implements Parcelable {
public static final int RESIZE_BOTH = RESIZE_HORIZONTAL | RESIZE_VERTICAL;
/**
+ * Indicates that the widget can be displayed on the home screen. This is the default value.
+ */
+ public static final int WIDGET_CATEGORY_HOME_SCREEN = 1;
+
+ /**
+ * Indicates that the widget can be displayed on the keyguard.
+ */
+ public static final int WIDGET_CATEGORY_KEYGUARD = 2;
+
+ /**
+ * Indicates that the widget supports no special features.
+ */
+ public static final int WIDGET_FEATURES_NONE = 0;
+
+ /**
+ * Indicates that the widget is output only, ie. has nothing clickable. This may be enforced by
+ * the host. Presently, this flag is used by the keyguard to indicate that it can be placed
+ * in the first position.
+ */
+ public static final int WIDGET_FEATURES_STATUS = 1;
+
+ /**
* Identity of this AppWidget component. This component should be a {@link
* android.content.BroadcastReceiver}, and it will be sent the AppWidget intents
* {@link android.appwidget as described in the AppWidget package documentation}.
@@ -111,6 +133,16 @@ public class AppWidgetProviderInfo implements Parcelable {
public int initialLayout;
/**
+ * The resource id of the initial layout for this AppWidget when it is displayed on keyguard.
+ * This parameter only needs to be provided if the widget can be displayed on the keyguard,
+ * see {@link #widgetCategory}.
+ *
+ * <p>This field corresponds to the <code>android:initialKeyguardLayout</code> attribute in
+ * the AppWidget meta-data file.
+ */
+ public int initialKeyguardLayout;
+
+ /**
* The activity to launch that will configure the AppWidget.
*
* <p>This class name of field corresponds to the <code>android:configure</code> attribute in
@@ -164,6 +196,26 @@ public class AppWidgetProviderInfo implements Parcelable {
*/
public int resizeMode;
+ /**
+ * Determines whether this widget can be displayed on the home screen, the keyguard, or both.
+ * A widget which is displayed on both needs to ensure that it follows the design guidelines
+ * for both widget classes. This can be achieved by querying the AppWidget options in its
+ * widget provider's update method.
+ *
+ * <p>This field corresponds to the <code>widgetCategory</code> attribute in
+ * the AppWidget meta-data file.
+ */
+ public int widgetCategory;
+
+ /**
+ * A field which specifies any special features that this widget supports. See
+ * {@link #WIDGET_FEATURES_NONE}, {@link #WIDGET_FEATURES_STATUS}.
+ *
+ * <p>This field corresponds to the <code>widgetFeatures</code> attribute in
+ * the AppWidget meta-data file.
+ */
+ public int widgetFeatures;
+
public AppWidgetProviderInfo() {
}
@@ -180,6 +232,7 @@ public class AppWidgetProviderInfo implements Parcelable {
this.minResizeHeight = in.readInt();
this.updatePeriodMillis = in.readInt();
this.initialLayout = in.readInt();
+ this.initialKeyguardLayout = in.readInt();
if (0 != in.readInt()) {
this.configure = new ComponentName(in);
}
@@ -188,6 +241,8 @@ public class AppWidgetProviderInfo implements Parcelable {
this.previewImage = in.readInt();
this.autoAdvanceViewId = in.readInt();
this.resizeMode = in.readInt();
+ this.widgetCategory = in.readInt();
+ this.widgetFeatures = in.readInt();
}
public void writeToParcel(android.os.Parcel out, int flags) {
@@ -203,6 +258,7 @@ public class AppWidgetProviderInfo implements Parcelable {
out.writeInt(this.minResizeHeight);
out.writeInt(this.updatePeriodMillis);
out.writeInt(this.initialLayout);
+ out.writeInt(this.initialKeyguardLayout);
if (this.configure != null) {
out.writeInt(1);
this.configure.writeToParcel(out, flags);
@@ -214,6 +270,30 @@ public class AppWidgetProviderInfo implements Parcelable {
out.writeInt(this.previewImage);
out.writeInt(this.autoAdvanceViewId);
out.writeInt(this.resizeMode);
+ out.writeInt(this.widgetCategory);
+ out.writeInt(this.widgetFeatures);
+ }
+
+ @Override
+ public AppWidgetProviderInfo clone() {
+ AppWidgetProviderInfo that = new AppWidgetProviderInfo();
+ that.provider = this.provider == null ? null : this.provider.clone();
+ that.minWidth = this.minWidth;
+ that.minHeight = this.minHeight;
+ that.minResizeWidth = this.minResizeHeight;
+ that.minResizeHeight = this.minResizeHeight;
+ that.updatePeriodMillis = this.updatePeriodMillis;
+ that.initialLayout = that.initialLayout;
+ that.initialKeyguardLayout = this.initialKeyguardLayout;
+ that.configure = this.configure == null ? null : this.configure.clone();
+ that.label = this.label == null ? null : this.label.substring(0);
+ that.icon = this.icon;
+ that.previewImage = this.previewImage;
+ that.autoAdvanceViewId = this.autoAdvanceViewId;
+ that.resizeMode = this.resizeMode;
+ that.widgetCategory = this.widgetCategory;
+ that.widgetFeatures = this.widgetFeatures;
+ return that;
}
public int describeContents() {
diff --git a/core/java/android/bluetooth/AtCommandHandler.java b/core/java/android/bluetooth/AtCommandHandler.java
deleted file mode 100644
index 6deab34..0000000
--- a/core/java/android/bluetooth/AtCommandHandler.java
+++ /dev/null
@@ -1,94 +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.bluetooth;
-
-import android.bluetooth.AtCommandResult;
-
-/**
- * Handler Interface for {@link AtParser}.<p>
- * @hide
- */
-public abstract class AtCommandHandler {
-
- /**
- * Handle Basic commands "ATA".<p>
- * These are single letter commands such as ATA and ATD. Anything following
- * the single letter command ('A' and 'D' respectively) will be passed as
- * 'arg'.<p>
- * For example, "ATDT1234" would result in the call
- * handleBasicCommand("T1234").<p>
- * @param arg Everything following the basic command character.
- * @return The result of this command.
- */
- public AtCommandResult handleBasicCommand(String arg) {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
-
- /**
- * Handle Actions command "AT+FOO".<p>
- * Action commands are part of the Extended command syntax, and are
- * typically used to signal an action on "FOO".<p>
- * @return The result of this command.
- */
- public AtCommandResult handleActionCommand() {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
-
- /**
- * Handle Read command "AT+FOO?".<p>
- * Read commands are part of the Extended command syntax, and are
- * typically used to read the value of "FOO".<p>
- * @return The result of this command.
- */
- public AtCommandResult handleReadCommand() {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
-
- /**
- * Handle Set command "AT+FOO=...".<p>
- * Set commands are part of the Extended command syntax, and are
- * typically used to set the value of "FOO". Multiple arguments can be
- * sent.<p>
- * AT+FOO=[<arg1>[,<arg2>[,...]]]<p>
- * Each argument will be either numeric (Integer) or String.
- * handleSetCommand is passed a generic Object[] array in which each
- * element will be an Integer (if it can be parsed with parseInt()) or
- * String.<p>
- * Missing arguments ",," are set to empty Strings.<p>
- * @param args Array of String and/or Integer's. There will always be at
- * least one element in this array.
- * @return The result of this command.
- */
- // Typically used to set this parameter
- public AtCommandResult handleSetCommand(Object[] args) {
- return new AtCommandResult(AtCommandResult.ERROR);
- }
-
- /**
- * Handle Test command "AT+FOO=?".<p>
- * Test commands are part of the Extended command syntax, and are typically
- * used to request an indication of the range of legal values that "FOO"
- * can take.<p>
- * By default we return an OK result, to indicate that this command is at
- * least recognized.<p>
- * @return The result of this command.
- */
- public AtCommandResult handleTestCommand() {
- return new AtCommandResult(AtCommandResult.OK);
- }
-
-}
diff --git a/core/java/android/bluetooth/AtCommandResult.java b/core/java/android/bluetooth/AtCommandResult.java
deleted file mode 100644
index 9675234..0000000
--- a/core/java/android/bluetooth/AtCommandResult.java
+++ /dev/null
@@ -1,115 +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.bluetooth;
-
-/**
- * The result of execution of a single AT command.<p>
- *
- *
- * This class can represent the final response to an AT command line, and also
- * intermediate responses to a single command within a chained AT command
- * line.<p>
- *
- * The actual responses that are intended to be send in reply to the AT command
- * line are stored in a string array. The final response is stored as an
- * int enum, converted to a string when toString() is called. Only a single
- * final response is sent from multiple commands chained into a single command
- * line.<p>
- * @hide
- */
-public class AtCommandResult {
- // Result code enumerations
- public static final int OK = 0;
- public static final int ERROR = 1;
- public static final int UNSOLICITED = 2;
-
- private static final String OK_STRING = "OK";
- private static final String ERROR_STRING = "ERROR";
-
- private int mResultCode; // Result code
- private StringBuilder mResponse; // Response with CRLF line breaks
-
- /**
- * Construct a new AtCommandResult with given result code, and an empty
- * response array.
- * @param resultCode One of OK, ERROR or UNSOLICITED.
- */
- public AtCommandResult(int resultCode) {
- mResultCode = resultCode;
- mResponse = new StringBuilder();
- }
-
- /**
- * Construct a new AtCommandResult with result code OK, and the specified
- * single line response.
- * @param response The single line response.
- */
- public AtCommandResult(String response) {
- this(OK);
- addResponse(response);
- }
-
- public int getResultCode() {
- return mResultCode;
- }
-
- /**
- * Add another line to the response.
- */
- public void addResponse(String response) {
- appendWithCrlf(mResponse, response);
- }
-
- /**
- * Add the given result into this AtCommandResult object.<p>
- * Used to combine results from multiple commands in a single command line
- * (command chaining).
- * @param result The AtCommandResult to add to this result.
- */
- public void addResult(AtCommandResult result) {
- if (result != null) {
- appendWithCrlf(mResponse, result.mResponse.toString());
- mResultCode = result.mResultCode;
- }
- }
-
- /**
- * Generate the string response ready to send
- */
- public String toString() {
- StringBuilder result = new StringBuilder(mResponse.toString());
- switch (mResultCode) {
- case OK:
- appendWithCrlf(result, OK_STRING);
- break;
- case ERROR:
- appendWithCrlf(result, ERROR_STRING);
- break;
- }
- return result.toString();
- }
-
- /** Append a string to a string builder, joining with a double
- * CRLF. Used to create multi-line AT command replies
- */
- public static void appendWithCrlf(StringBuilder str1, String str2) {
- if (str1.length() > 0 && str2.length() > 0) {
- str1.append("\r\n\r\n");
- }
- str1.append(str2);
- }
-};
diff --git a/core/java/android/bluetooth/AtParser.java b/core/java/android/bluetooth/AtParser.java
deleted file mode 100644
index 328fb2b..0000000
--- a/core/java/android/bluetooth/AtParser.java
+++ /dev/null
@@ -1,367 +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.bluetooth;
-
-import java.util.*;
-
-/**
- * An AT (Hayes command) Parser based on (a subset of) the ITU-T V.250 standard.
- * <p>
- *
- * Conformant with the subset of V.250 required for implementation of the
- * Bluetooth Headset and Handsfree Profiles, as per Bluetooth SIP
- * specifications. Also implements some V.250 features not required by
- * Bluetooth - such as chained commands.<p>
- *
- * Command handlers are registered with an AtParser object. These handlers are
- * invoked when command lines are processed by AtParser's process() method.<p>
- *
- * The AtParser object accepts a new command line to parse via its process()
- * method. It breaks each command line into one or more commands. Each command
- * is parsed for name, type, and (optional) arguments, and an appropriate
- * external handler method is called through the AtCommandHandler interface.
- *
- * The command types are<ul>
- * <li>Basic Command. For example "ATDT1234567890". Basic command names are a
- * single character (e.g. "D"), and everything following this character is
- * passed to the handler as a string argument (e.g. "T1234567890").
- * <li>Action Command. For example "AT+CIMI". The command name is "CIMI", and
- * there are no arguments for action commands.
- * <li>Read Command. For example "AT+VGM?". The command name is "VGM", and there
- * are no arguments for get commands.
- * <li>Set Command. For example "AT+VGM=14". The command name is "VGM", and
- * there is a single integer argument in this case. In the general case then
- * can be zero or more arguments (comma delimited) each of integer or string
- * form.
- * <li>Test Command. For example "AT+VGM=?. No arguments.
- * </ul>
- *
- * In V.250 the last four command types are known as Extended Commands, and
- * they are used heavily in Bluetooth.<p>
- *
- * Basic commands cannot be chained in this implementation. For Bluetooth
- * headset/handsfree use this is acceptable, because they only use the basic
- * commands ATA and ATD, which are not allowed to be chained. For general V.250
- * use we would need to improve this class to allow Basic command chaining -
- * however it's tricky to get right because there is no delimiter for Basic
- * command chaining.<p>
- *
- * Extended commands can be chained. For example:<p>
- * AT+VGM?;+VGM=14;+CIMI<p>
- * This is equivalent to:<p>
- * AT+VGM?
- * AT+VGM=14
- * AT+CIMI
- * Except that only one final result code is return (although several
- * intermediate responses may be returned), and as soon as one command in the
- * chain fails the rest are abandoned.<p>
- *
- * Handlers are registered by there command name via register(Char c, ...) or
- * register(String s, ...). Handlers for Basic command should be registered by
- * the basic command character, and handlers for Extended commands should be
- * registered by String.<p>
- *
- * Refer to:<ul>
- * <li>ITU-T Recommendation V.250
- * <li>ETSI TS 127.007 (AT Command set for User Equipment, 3GPP TS 27.007)
- * <li>Bluetooth Headset Profile Spec (K6)
- * <li>Bluetooth Handsfree Profile Spec (HFP 1.5)
- * </ul>
- * @hide
- */
-public class AtParser {
-
- // Extended command type enumeration, only used internally
- private static final int TYPE_ACTION = 0; // AT+FOO
- private static final int TYPE_READ = 1; // AT+FOO?
- private static final int TYPE_SET = 2; // AT+FOO=
- private static final int TYPE_TEST = 3; // AT+FOO=?
-
- private HashMap<String, AtCommandHandler> mExtHandlers;
- private HashMap<Character, AtCommandHandler> mBasicHandlers;
-
- private String mLastInput; // for "A/" (repeat last command) support
-
- /**
- * Create a new AtParser.<p>
- * No handlers are registered.
- */
- public AtParser() {
- mBasicHandlers = new HashMap<Character, AtCommandHandler>();
- mExtHandlers = new HashMap<String, AtCommandHandler>();
- mLastInput = "";
- }
-
- /**
- * Register a Basic command handler.<p>
- * Basic command handlers are later called via their
- * <code>handleBasicCommand(String args)</code> method.
- * @param command Command name - a single character
- * @param handler Handler to register
- */
- public void register(Character command, AtCommandHandler handler) {
- mBasicHandlers.put(command, handler);
- }
-
- /**
- * Register an Extended command handler.<p>
- * Extended command handlers are later called via:<ul>
- * <li><code>handleActionCommand()</code>
- * <li><code>handleGetCommand()</code>
- * <li><code>handleSetCommand()</code>
- * <li><code>handleTestCommand()</code>
- * </ul>
- * Only one method will be called for each command processed.
- * @param command Command name - can be multiple characters
- * @param handler Handler to register
- */
- public void register(String command, AtCommandHandler handler) {
- mExtHandlers.put(command, handler);
- }
-
-
- /**
- * Strip input of whitespace and force Uppercase - except sections inside
- * quotes. Also fixes unmatched quotes (by appending a quote). Double
- * quotes " are the only quotes allowed by V.250
- */
- static private String clean(String input) {
- StringBuilder out = new StringBuilder(input.length());
-
- for (int i = 0; i < input.length(); i++) {
- char c = input.charAt(i);
- if (c == '"') {
- int j = input.indexOf('"', i + 1 ); // search for closing "
- if (j == -1) { // unmatched ", insert one.
- out.append(input.substring(i, input.length()));
- out.append('"');
- break;
- }
- out.append(input.substring(i, j + 1));
- i = j;
- } else if (c != ' ') {
- out.append(Character.toUpperCase(c));
- }
- }
-
- return out.toString();
- }
-
- static private boolean isAtoZ(char c) {
- return (c >= 'A' && c <= 'Z');
- }
-
- /**
- * Find a character ch, ignoring quoted sections.
- * Return input.length() if not found.
- */
- static private int findChar(char ch, String input, int fromIndex) {
- for (int i = fromIndex; i < input.length(); i++) {
- char c = input.charAt(i);
- if (c == '"') {
- i = input.indexOf('"', i + 1);
- if (i == -1) {
- return input.length();
- }
- } else if (c == ch) {
- return i;
- }
- }
- return input.length();
- }
-
- /**
- * Break an argument string into individual arguments (comma delimited).
- * Integer arguments are turned into Integer objects. Otherwise a String
- * object is used.
- */
- static private Object[] generateArgs(String input) {
- int i = 0;
- int j;
- ArrayList<Object> out = new ArrayList<Object>();
- while (i <= input.length()) {
- j = findChar(',', input, i);
-
- String arg = input.substring(i, j);
- try {
- out.add(new Integer(arg));
- } catch (NumberFormatException e) {
- out.add(arg);
- }
-
- i = j + 1; // move past comma
- }
- return out.toArray();
- }
-
- /**
- * Return the index of the end of character after the last character in
- * the extended command name. Uses the V.250 spec for allowed command
- * names.
- */
- static private int findEndExtendedName(String input, int index) {
- for (int i = index; i < input.length(); i++) {
- char c = input.charAt(i);
-
- // V.250 defines the following chars as legal extended command
- // names
- if (isAtoZ(c)) continue;
- if (c >= '0' && c <= '9') continue;
- switch (c) {
- case '!':
- case '%':
- case '-':
- case '.':
- case '/':
- case ':':
- case '_':
- continue;
- default:
- return i;
- }
- }
- return input.length();
- }
-
- /**
- * Processes an incoming AT command line.<p>
- * This method will invoke zero or one command handler methods for each
- * command in the command line.<p>
- * @param raw_input The AT input, without EOL delimiter (e.g. <CR>).
- * @return Result object for this command line. This can be
- * converted to a String[] response with toStrings().
- */
- public AtCommandResult process(String raw_input) {
- String input = clean(raw_input);
-
- // Handle "A/" (repeat previous line)
- if (input.regionMatches(0, "A/", 0, 2)) {
- input = new String(mLastInput);
- } else {
- mLastInput = new String(input);
- }
-
- // Handle empty line - no response necessary
- if (input.equals("")) {
- // Return []
- return new AtCommandResult(AtCommandResult.UNSOLICITED);
- }
-
- // Anything else deserves an error
- if (!input.regionMatches(0, "AT", 0, 2)) {
- // Return ["ERROR"]
- return new AtCommandResult(AtCommandResult.ERROR);
- }
-
- // Ok we have a command that starts with AT. Process it
- int index = 2;
- AtCommandResult result =
- new AtCommandResult(AtCommandResult.UNSOLICITED);
- while (index < input.length()) {
- char c = input.charAt(index);
-
- if (isAtoZ(c)) {
- // Option 1: Basic Command
- // Pass the rest of the line as is to the handler. Do not
- // look for any more commands on this line.
- String args = input.substring(index + 1);
- if (mBasicHandlers.containsKey((Character)c)) {
- result.addResult(mBasicHandlers.get(
- (Character)c).handleBasicCommand(args));
- return result;
- } else {
- // no handler
- result.addResult(
- new AtCommandResult(AtCommandResult.ERROR));
- return result;
- }
- // control never reaches here
- }
-
- if (c == '+') {
- // Option 2: Extended Command
- // Search for first non-name character. Short-circuit if
- // we don't handle this command name.
- int i = findEndExtendedName(input, index + 1);
- String commandName = input.substring(index, i);
- if (!mExtHandlers.containsKey(commandName)) {
- // no handler
- result.addResult(
- new AtCommandResult(AtCommandResult.ERROR));
- return result;
- }
- AtCommandHandler handler = mExtHandlers.get(commandName);
-
- // Search for end of this command - this is usually the end of
- // line
- int endIndex = findChar(';', input, index);
-
- // Determine what type of command this is.
- // Default to TYPE_ACTION if we can't find anything else
- // obvious.
- int type;
-
- if (i >= endIndex) {
- type = TYPE_ACTION;
- } else if (input.charAt(i) == '?') {
- type = TYPE_READ;
- } else if (input.charAt(i) == '=') {
- if (i + 1 < endIndex) {
- if (input.charAt(i + 1) == '?') {
- type = TYPE_TEST;
- } else {
- type = TYPE_SET;
- }
- } else {
- type = TYPE_SET;
- }
- } else {
- type = TYPE_ACTION;
- }
-
- // Call this command. Short-circuit as soon as a command fails
- switch (type) {
- case TYPE_ACTION:
- result.addResult(handler.handleActionCommand());
- break;
- case TYPE_READ:
- result.addResult(handler.handleReadCommand());
- break;
- case TYPE_TEST:
- result.addResult(handler.handleTestCommand());
- break;
- case TYPE_SET:
- Object[] args =
- generateArgs(input.substring(i + 1, endIndex));
- result.addResult(handler.handleSetCommand(args));
- break;
- }
- if (result.getResultCode() != AtCommandResult.OK) {
- return result; // short-circuit
- }
-
- index = endIndex;
- } else {
- // Can't tell if this is a basic or extended command.
- // Push forwards and hope we hit something.
- index++;
- }
- }
- // Finished processing (and all results were ok)
- return result;
- }
-}
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 7300107..6e2278d 100644..100755
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -18,12 +18,13 @@ package android.bluetooth;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.server.BluetoothA2dpService;
import android.util.Log;
import java.util.ArrayList;
@@ -43,7 +44,7 @@ import java.util.List;
*/
public final class BluetoothA2dp implements BluetoothProfile {
private static final String TAG = "BluetoothA2dp";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
/**
* Intent used to broadcast the change in connection state of the A2DP
@@ -102,37 +103,90 @@ public final class BluetoothA2dp implements BluetoothProfile {
*/
public static final int STATE_NOT_PLAYING = 11;
+ private Context mContext;
private ServiceListener mServiceListener;
private IBluetoothA2dp mService;
private BluetoothAdapter mAdapter;
+ final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (!up) {
+ if (DBG) 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 (DBG) 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");
+ }
+ }
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
+ }
+ }
+ }
+ }
+ };
/**
* Create a BluetoothA2dp proxy object for interacting with the local
* Bluetooth A2DP service.
*
*/
- /*package*/ BluetoothA2dp(Context mContext, ServiceListener l) {
- IBinder b = ServiceManager.getService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE);
+ /*package*/ BluetoothA2dp(Context context, ServiceListener l) {
+ mContext = context;
mServiceListener = l;
mAdapter = BluetoothAdapter.getDefaultAdapter();
- if (b != null) {
- mService = IBluetoothA2dp.Stub.asInterface(b);
- if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothProfile.A2DP, this);
+ IBluetoothManager mgr = mAdapter.getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
}
- } else {
- Log.w(TAG, "Bluetooth A2DP service not available!");
+ }
- // Instead of throwing an exception which prevents people from going
- // into Wireless settings in the emulator. Let it crash later when it is actually used.
- mService = null;
+ if (!context.bindService(new Intent(IBluetoothA2dp.class.getName()), mConnection, 0)) {
+ Log.e(TAG, "Could not bind to Bluetooth A2DP Service");
}
}
/*package*/ void close() {
mServiceListener = null;
+ 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);
+ }
+ }
+ }
}
+ public void finalize() {
+ close();
+ }
/**
* Initiate connection to a profile of the remote bluetooth device.
*
@@ -267,7 +321,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* Set priority of the profile
*
* <p> The device should already be paired.
- * Priority can be one of {@link #PRIORITY_ON} or
+ * Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager
* {@link #PRIORITY_OFF},
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
@@ -283,7 +337,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
if (mService != null && isEnabled()
&& isValidDevice(device)) {
if (priority != BluetoothProfile.PRIORITY_OFF &&
- priority != BluetoothProfile.PRIORITY_ON) {
+ priority != BluetoothProfile.PRIORITY_ON){
return false;
}
try {
@@ -347,67 +401,6 @@ public final class BluetoothA2dp implements BluetoothProfile {
}
/**
- * Initiate suspend from an A2DP sink.
- *
- * <p> This API will return false in scenarios like the A2DP
- * device is not in connected state etc. When this API returns,
- * true, it is guaranteed that {@link #ACTION_CONNECTION_STATE_CHANGED}
- * intent will be broadcasted with the state. Users can get the
- * state of the A2DP device from this intent.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
- *
- * @param device Remote A2DP sink
- * @return false on immediate error,
- * true otherwise
- * @hide
- */
- public boolean suspendSink(BluetoothDevice device) {
- if (mService != null && isEnabled()
- && isValidDevice(device)) {
- try {
- return mService.suspendSink(device);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
- }
- }
- if (mService == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- }
-
- /**
- * Initiate resume from a suspended A2DP sink.
- *
- * <p> This API will return false in scenarios like the A2DP
- * device is not in suspended state etc. When this API returns,
- * true, it is guaranteed that {@link #ACTION_SINK_STATE_CHANGED}
- * intent will be broadcasted with the state. Users can get the
- * state of the A2DP device from this intent.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- *
- * @param device Remote A2DP sink
- * @return false on immediate error,
- * true otherwise
- * @hide
- */
- public boolean resumeSink(BluetoothDevice device) {
- if (mService != null && isEnabled()
- && isValidDevice(device)) {
- try {
- return mService.resumeSink(device);
- } catch (RemoteException e) {
- Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
- }
- }
- if (mService == null) Log.w(TAG, "Proxy not attached to service");
- return false;
- }
-
- /**
* This function checks if the remote device is an AVCRP
* target and thus whether we should send volume keys
* changes or not.
@@ -428,23 +421,6 @@ public final class BluetoothA2dp implements BluetoothProfile {
}
/**
- * Allow or disallow incoming connection
- * @param device Sink
- * @param value True / False
- * @return Success or Failure of the binder call.
- * @hide
- */
- public boolean allowIncomingConnect(BluetoothDevice device, boolean value) {
- if (DBG) log("allowIncomingConnect(" + device + ":" + value + ")");
- try {
- return mService.allowIncomingConnect(device, value);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return false;
- }
- }
-
- /**
* Helper for converting a state to a string.
*
* For debug use only - strings are not internationalized.
@@ -469,6 +445,24 @@ public final class BluetoothA2dp implements BluetoothProfile {
}
}
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if (DBG) Log.d(TAG, "Proxy object connected");
+ mService = IBluetoothA2dp.Stub.asInterface(service);
+
+ if (mServiceListener != null) {
+ mServiceListener.onServiceConnected(BluetoothProfile.A2DP, BluetoothA2dp.this);
+ }
+ }
+ public void onServiceDisconnected(ComponentName className) {
+ if (DBG) Log.d(TAG, "Proxy object disconnected");
+ mService = null;
+ if (mServiceListener != null) {
+ mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP);
+ }
+ }
+ };
+
private boolean isEnabled() {
if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
return false;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 8e3df47..f817fb4 100644..100755
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -31,6 +31,7 @@ import android.util.Log;
import android.util.Pair;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -73,7 +74,8 @@ import java.util.UUID;
*/
public final class BluetoothAdapter {
private static final String TAG = "BluetoothAdapter";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false;
/**
* Sentinel error value for this class. Guaranteed to not equal any other
@@ -343,7 +345,7 @@ public final class BluetoothAdapter {
public static final int STATE_DISCONNECTING = 3;
/** @hide */
- public static final String BLUETOOTH_SERVICE = "bluetooth";
+ public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
private static final int ADDRESS_LENGTH = 17;
@@ -353,7 +355,8 @@ public final class BluetoothAdapter {
*/
private static BluetoothAdapter sAdapter;
- private final IBluetooth mService;
+ private final IBluetoothManager mManagerService;
+ private IBluetooth mService;
private Handler mServiceRecordHandler;
@@ -367,10 +370,12 @@ public final class BluetoothAdapter {
*/
public static synchronized BluetoothAdapter getDefaultAdapter() {
if (sAdapter == null) {
- IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
+ IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE);
if (b != null) {
- IBluetooth service = IBluetooth.Stub.asInterface(b);
- sAdapter = new BluetoothAdapter(service);
+ IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b);
+ sAdapter = new BluetoothAdapter(managerService);
+ } else {
+ Log.e(TAG, "Bluetooth binder is null");
}
}
return sAdapter;
@@ -378,13 +383,16 @@ public final class BluetoothAdapter {
/**
* Use {@link #getDefaultAdapter} to get the BluetoothAdapter instance.
- * @hide
*/
- public BluetoothAdapter(IBluetooth service) {
- if (service == null) {
- throw new IllegalArgumentException("service is null");
+ BluetoothAdapter(IBluetoothManager managerService) {
+
+ if (managerService == null) {
+ throw new IllegalArgumentException("bluetooth manager service is null");
}
- mService = service;
+ try {
+ mService = managerService.registerAdapter(mManagerCallback);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ mManagerService = managerService;
mServiceRecordHandler = null;
}
@@ -432,8 +440,11 @@ public final class BluetoothAdapter {
* @return true if the local adapter is turned on
*/
public boolean isEnabled() {
+
try {
- return mService.isEnabled();
+ synchronized(mManagerCallback) {
+ if (mService != null) return mService.isEnabled();
+ }
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -451,8 +462,18 @@ public final class BluetoothAdapter {
*/
public int getState() {
try {
- return mService.getBluetoothState();
+ synchronized(mManagerCallback) {
+ if (mService != null)
+ {
+ int state= mService.getState();
+ if (VDBG) Log.d(TAG, "" + hashCode() + ": getState(). Returning " + state);
+ return state;
+ }
+ // TODO(BT) there might be a small gap during STATE_TURNING_ON that
+ // mService is null, handle that case
+ }
} catch (RemoteException e) {Log.e(TAG, "", e);}
+ if (DBG) Log.d(TAG, "" + hashCode() + ": getState() : mService = null. Returning STATE_OFF");
return STATE_OFF;
}
@@ -484,8 +505,12 @@ public final class BluetoothAdapter {
* immediate error
*/
public boolean enable() {
+ if (isEnabled() == true){
+ if (DBG) Log.d(TAG, "enable(): BT is already enabled..!");
+ return true;
+ }
try {
- return mService.enable();
+ return mManagerService.enable();
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -516,7 +541,25 @@ public final class BluetoothAdapter {
*/
public boolean disable() {
try {
- return mService.disable(true);
+ return mManagerService.disable(true);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ /**
+ * Turn off the local Bluetooth adapter and don't persist the setting.
+ *
+ * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * permission
+ *
+ * @return true to indicate adapter shutdown has begun, or false on
+ * immediate error
+ * @hide
+ */
+ public boolean disable(boolean persist) {
+
+ try {
+ return mManagerService.disable(persist);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -530,7 +573,7 @@ public final class BluetoothAdapter {
*/
public String getAddress() {
try {
- return mService.getAddress();
+ return mManagerService.getAddress();
} catch (RemoteException e) {Log.e(TAG, "", e);}
return null;
}
@@ -544,7 +587,7 @@ public final class BluetoothAdapter {
*/
public String getName() {
try {
- return mService.getName();
+ return mManagerService.getName();
} catch (RemoteException e) {Log.e(TAG, "", e);}
return null;
}
@@ -560,7 +603,9 @@ public final class BluetoothAdapter {
public ParcelUuid[] getUuids() {
if (getState() != STATE_ON) return null;
try {
- return mService.getUuids();
+ synchronized(mManagerCallback) {
+ if (mService != null) return mService.getUuids();
+ }
} catch (RemoteException e) {Log.e(TAG, "", e);}
return null;
}
@@ -583,7 +628,9 @@ public final class BluetoothAdapter {
public boolean setName(String name) {
if (getState() != STATE_ON) return false;
try {
- return mService.setName(name);
+ synchronized(mManagerCallback) {
+ if (mService != null) return mService.setName(name);
+ }
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -607,7 +654,9 @@ public final class BluetoothAdapter {
public int getScanMode() {
if (getState() != STATE_ON) return SCAN_MODE_NONE;
try {
- return mService.getScanMode();
+ synchronized(mManagerCallback) {
+ if (mService != null) return mService.getScanMode();
+ }
} catch (RemoteException e) {Log.e(TAG, "", e);}
return SCAN_MODE_NONE;
}
@@ -643,7 +692,9 @@ public final class BluetoothAdapter {
public boolean setScanMode(int mode, int duration) {
if (getState() != STATE_ON) return false;
try {
- return mService.setScanMode(mode, duration);
+ synchronized(mManagerCallback) {
+ if (mService != null) return mService.setScanMode(mode, duration);
+ }
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -651,14 +702,17 @@ public final class BluetoothAdapter {
/** @hide */
public boolean setScanMode(int mode) {
if (getState() != STATE_ON) return false;
- return setScanMode(mode, 120);
+ /* getDiscoverableTimeout() to use the latest from NV than use 0 */
+ return setScanMode(mode, getDiscoverableTimeout());
}
/** @hide */
public int getDiscoverableTimeout() {
if (getState() != STATE_ON) return -1;
try {
- return mService.getDiscoverableTimeout();
+ synchronized(mManagerCallback) {
+ if (mService != null) return mService.getDiscoverableTimeout();
+ }
} catch (RemoteException e) {Log.e(TAG, "", e);}
return -1;
}
@@ -667,7 +721,9 @@ public final class BluetoothAdapter {
public void setDiscoverableTimeout(int timeout) {
if (getState() != STATE_ON) return;
try {
- mService.setDiscoverableTimeout(timeout);
+ synchronized(mManagerCallback) {
+ if (mService != null) mService.setDiscoverableTimeout(timeout);
+ }
} catch (RemoteException e) {Log.e(TAG, "", e);}
}
@@ -704,7 +760,9 @@ public final class BluetoothAdapter {
public boolean startDiscovery() {
if (getState() != STATE_ON) return false;
try {
- return mService.startDiscovery();
+ synchronized(mManagerCallback) {
+ if (mService != null) return mService.startDiscovery();
+ }
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -729,7 +787,9 @@ public final class BluetoothAdapter {
public boolean cancelDiscovery() {
if (getState() != STATE_ON) return false;
try {
- return mService.cancelDiscovery();
+ synchronized(mManagerCallback) {
+ if (mService != null) return mService.cancelDiscovery();
+ }
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -756,7 +816,9 @@ public final class BluetoothAdapter {
public boolean isDiscovering() {
if (getState() != STATE_ON) return false;
try {
- return mService.isDiscovering();
+ synchronized(mManagerCallback) {
+ if (mService != null ) return mService.isDiscovering();
+ }
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -774,10 +836,13 @@ public final class BluetoothAdapter {
*/
public Set<BluetoothDevice> getBondedDevices() {
if (getState() != STATE_ON) {
- return toDeviceSet(new String[0]);
+ return toDeviceSet(new BluetoothDevice[0]);
}
try {
- return toDeviceSet(mService.listBonds());
+ synchronized(mManagerCallback) {
+ if (mService != null) return toDeviceSet(mService.getBondedDevices());
+ }
+ return toDeviceSet(new BluetoothDevice[0]);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return null;
}
@@ -798,7 +863,9 @@ public final class BluetoothAdapter {
public int getConnectionState() {
if (getState() != STATE_ON) return BluetoothAdapter.STATE_DISCONNECTED;
try {
- return mService.getAdapterConnectionState();
+ synchronized(mManagerCallback) {
+ if (mService != null) return mService.getAdapterConnectionState();
+ }
} catch (RemoteException e) {Log.e(TAG, "getConnectionState:", e);}
return BluetoothAdapter.STATE_DISCONNECTED;
}
@@ -821,7 +888,9 @@ public final class BluetoothAdapter {
public int getProfileConnectionState(int profile) {
if (getState() != STATE_ON) return BluetoothProfile.STATE_DISCONNECTED;
try {
- return mService.getProfileConnectionState(profile);
+ synchronized(mManagerCallback) {
+ if (mService != null) return mService.getProfileConnectionState(profile);
+ }
} catch (RemoteException e) {
Log.e(TAG, "getProfileConnectionState:", e);
}
@@ -829,51 +898,6 @@ public final class BluetoothAdapter {
}
/**
- /**
- * Picks RFCOMM channels until none are left.
- * Avoids reserved channels.
- */
- private static class RfcommChannelPicker {
- private static final int[] RESERVED_RFCOMM_CHANNELS = new int[] {
- 10, // HFAG
- 11, // HSAG
- 12, // OPUSH
- 19, // PBAP
- };
- private static LinkedList<Integer> sChannels; // master list of non-reserved channels
- private static Random sRandom;
-
- private final LinkedList<Integer> mChannels; // local list of channels left to try
-
- private final UUID mUuid;
-
- public RfcommChannelPicker(UUID uuid) {
- synchronized (RfcommChannelPicker.class) {
- if (sChannels == null) {
- // lazy initialization of non-reserved rfcomm channels
- sChannels = new LinkedList<Integer>();
- for (int i = 1; i <= BluetoothSocket.MAX_RFCOMM_CHANNEL; i++) {
- sChannels.addLast(new Integer(i));
- }
- for (int reserved : RESERVED_RFCOMM_CHANNELS) {
- sChannels.remove(new Integer(reserved));
- }
- sRandom = new Random();
- }
- mChannels = (LinkedList<Integer>)sChannels.clone();
- }
- mUuid = uuid;
- }
- /* Returns next random channel, or -1 if we're out */
- public int nextChannel() {
- if (mChannels.size() == 0) {
- return -1;
- }
- return mChannels.remove(sRandom.nextInt(mChannels.size()));
- }
- }
-
- /**
* Create a listening, secure RFCOMM Bluetooth socket.
* <p>A remote device connecting to this socket will be authenticated and
* communication on this socket will be encrypted.
@@ -892,10 +916,10 @@ public final class BluetoothAdapter {
BluetoothSocket.TYPE_RFCOMM, true, true, channel);
int errno = socket.mSocket.bindListen();
if (errno != 0) {
- try {
- socket.close();
- } catch (IOException e) {}
- socket.mSocket.throwErrnoNative(errno);
+ //TODO(BT): Throw the same exception error code
+ // that the previous code was using.
+ //socket.mSocket.throwErrnoNative(errno);
+ throw new IOException("Error: " + errno);
}
return socket;
}
@@ -996,70 +1020,23 @@ public final class BluetoothAdapter {
return createNewRfcommSocketAndRecord(name, uuid, false, true);
}
+
private BluetoothServerSocket createNewRfcommSocketAndRecord(String name, UUID uuid,
boolean auth, boolean encrypt) throws IOException {
- RfcommChannelPicker picker = new RfcommChannelPicker(uuid);
-
BluetoothServerSocket socket;
- int channel;
- int errno;
- while (true) {
- channel = picker.nextChannel();
-
- if (channel == -1) {
- throw new IOException("No available channels");
- }
-
- socket = new BluetoothServerSocket(
- BluetoothSocket.TYPE_RFCOMM, auth, encrypt, channel);
- errno = socket.mSocket.bindListen();
- if (errno == 0) {
- if (DBG) Log.d(TAG, "listening on RFCOMM channel " + channel);
- break; // success
- } else if (errno == BluetoothSocket.EADDRINUSE) {
- if (DBG) Log.d(TAG, "RFCOMM channel " + channel + " in use");
- try {
- socket.close();
- } catch (IOException e) {}
- continue; // try another channel
- } else {
- try {
- socket.close();
- } catch (IOException e) {}
- socket.mSocket.throwErrnoNative(errno); // Exception as a result of bindListen()
- }
- }
-
- int handle = -1;
- try {
- handle = mService.addRfcommServiceRecord(name, new ParcelUuid(uuid), channel,
- new Binder());
- } catch (RemoteException e) {Log.e(TAG, "", e);}
- if (handle == -1) {
- try {
- socket.close();
- } catch (IOException e) {}
- throw new IOException("Not able to register SDP record for " + name);
- }
-
- if (mServiceRecordHandler == null) {
- mServiceRecordHandler = new Handler(Looper.getMainLooper()) {
- public void handleMessage(Message msg) {
- /* handle socket closing */
- int handle = msg.what;
- try {
- if (DBG) Log.d(TAG, "Removing service record " +
- Integer.toHexString(handle));
- mService.removeServiceRecord(handle);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
- }
- };
+ socket = new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, auth,
+ encrypt, new ParcelUuid(uuid));
+ socket.setServiceName(name);
+ int errno = socket.mSocket.bindListen();
+ if (errno != 0) {
+ //TODO(BT): Throw the same exception error code
+ // that the previous code was using.
+ //socket.mSocket.throwErrnoNative(errno);
+ throw new IOException("Error: " + errno);
}
- socket.setCloseHandler(mServiceRecordHandler, handle);
return socket;
}
-
/**
* Construct an unencrypted, unauthenticated, RFCOMM server socket.
* Call #accept to retrieve connections to this socket.
@@ -1073,10 +1050,10 @@ public final class BluetoothAdapter {
BluetoothSocket.TYPE_RFCOMM, false, false, port);
int errno = socket.mSocket.bindListen();
if (errno != 0) {
- try {
- socket.close();
- } catch (IOException e) {}
- socket.mSocket.throwErrnoNative(errno);
+ //TODO(BT): Throw the same exception error code
+ // that the previous code was using.
+ //socket.mSocket.throwErrnoNative(errno);
+ throw new IOException("Error: " + errno);
}
return socket;
}
@@ -1094,11 +1071,11 @@ public final class BluetoothAdapter {
BluetoothServerSocket socket = new BluetoothServerSocket(
BluetoothSocket.TYPE_RFCOMM, false, true, port);
int errno = socket.mSocket.bindListen();
- if (errno != 0) {
- try {
- socket.close();
- } catch (IOException e) {}
- socket.mSocket.throwErrnoNative(errno);
+ if (errno < 0) {
+ //TODO(BT): Throw the same exception error code
+ // that the previous code was using.
+ //socket.mSocket.throwErrnoNative(errno);
+ throw new IOException("Error: " + errno);
}
return socket;
}
@@ -1115,11 +1092,10 @@ public final class BluetoothAdapter {
BluetoothServerSocket socket = new BluetoothServerSocket(
BluetoothSocket.TYPE_SCO, false, false, -1);
int errno = socket.mSocket.bindListen();
- if (errno != 0) {
- try {
- socket.close();
- } catch (IOException e) {}
- socket.mSocket.throwErrnoNative(errno);
+ if (errno < 0) {
+ //TODO(BT): Throw the same exception error code
+ // that the previous code was using.
+ //socket.mSocket.throwErrnoNative(errno);
}
return socket;
}
@@ -1134,6 +1110,8 @@ public final class BluetoothAdapter {
*/
public Pair<byte[], byte[]> readOutOfBandData() {
if (getState() != STATE_ON) return null;
+ //TODO(BT
+ /*
try {
byte[] hash;
byte[] randomizer;
@@ -1151,7 +1129,7 @@ public final class BluetoothAdapter {
}
return new Pair<byte[], byte[]>(hash, randomizer);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
+ } catch (RemoteException e) {Log.e(TAG, "", e);}*/
return null;
}
@@ -1231,14 +1209,53 @@ public final class BluetoothAdapter {
}
}
+ final private IBluetoothManagerCallback mManagerCallback =
+ new IBluetoothManagerCallback.Stub() {
+ public void onBluetoothServiceUp(IBluetooth bluetoothService) {
+ if (DBG) Log.d(TAG, "onBluetoothServiceUp: " + bluetoothService);
+ synchronized (mManagerCallback) {
+ mService = bluetoothService;
+ for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){
+ try {
+ if (cb != null) {
+ cb.onBluetoothServiceUp(bluetoothService);
+ } else {
+ Log.d(TAG, "onBluetoothServiceUp: cb is null!!!");
+ }
+ } catch (Exception e) { Log.e(TAG,"",e);}
+ }
+ }
+ }
+
+ public void onBluetoothServiceDown() {
+ if (DBG) Log.d(TAG, "onBluetoothServiceDown: " + mService);
+ synchronized (mManagerCallback) {
+ mService = null;
+ for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks ){
+ try {
+ if (cb != null) {
+ cb.onBluetoothServiceDown();
+ } else {
+ Log.d(TAG, "onBluetoothServiceDown: cb is null!!!");
+ }
+ } catch (Exception e) { Log.e(TAG,"",e);}
+ }
+ }
+ }
+ };
+
/**
* Enable the Bluetooth Adapter, but don't auto-connect devices
* and don't persist state. Only for use by system applications.
* @hide
*/
public boolean enableNoAutoConnect() {
+ if (isEnabled() == true){
+ if (DBG) Log.d(TAG, "enableNoAutoConnect(): BT is already enabled..!");
+ return true;
+ }
try {
- return mService.enableNoAutoConnect();
+ return mManagerService.enableNoAutoConnect();
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -1276,12 +1293,14 @@ public final class BluetoothAdapter {
BluetoothStateChangeCallback callback) {
if (callback == null) return false;
+ //TODO(BT)
+ /*
try {
return mService.changeApplicationBluetoothState(on, new
StateChangeCallbackWrapper(callback), new Binder());
} catch (RemoteException e) {
Log.e(TAG, "changeBluetoothState", e);
- }
+ }*/
return false;
}
@@ -1309,14 +1328,22 @@ public final class BluetoothAdapter {
}
}
- private Set<BluetoothDevice> toDeviceSet(String[] addresses) {
- Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>(addresses.length);
- for (int i = 0; i < addresses.length; i++) {
- devices.add(getRemoteDevice(addresses[i]));
+ private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) {
+ Set<BluetoothDevice> deviceSet = new HashSet<BluetoothDevice>(Arrays.asList(devices));
+ return Collections.unmodifiableSet(deviceSet);
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ mManagerService.unregisterAdapter(mManagerCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ } finally {
+ super.finalize();
}
- return Collections.unmodifiableSet(devices);
}
+
/**
* Validate a String Bluetooth address, such as "00:43:A8:23:10:F0"
* <p>Alphabetic characters must be uppercase to be valid.
@@ -1347,4 +1374,27 @@ public final class BluetoothAdapter {
}
return true;
}
+
+ /*package*/ IBluetoothManager getBluetoothManager() {
+ return mManagerService;
+ }
+
+ private ArrayList<IBluetoothManagerCallback> mProxyServiceStateCallbacks = new ArrayList<IBluetoothManagerCallback>();
+
+ /*package*/ IBluetooth getBluetoothService(IBluetoothManagerCallback cb) {
+ synchronized (mManagerCallback) {
+ if (cb == null) {
+ Log.w(TAG, "getBluetoothService() called with no BluetoothManagerCallback");
+ } else if (!mProxyServiceStateCallbacks.contains(cb)) {
+ mProxyServiceStateCallbacks.add(cb);
+ }
+ }
+ return mService;
+ }
+
+ /*package*/ void removeServiceStateCallback(IBluetoothManagerCallback cb) {
+ synchronized (mManagerCallback) {
+ mProxyServiceStateCallbacks.remove(cb);
+ }
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothAudioGateway.java b/core/java/android/bluetooth/BluetoothAudioGateway.java
deleted file mode 100644
index 9351393..0000000
--- a/core/java/android/bluetooth/BluetoothAudioGateway.java
+++ /dev/null
@@ -1,202 +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.bluetooth;
-
-import java.lang.Thread;
-
-import android.os.Message;
-import android.os.Handler;
-import android.util.Log;
-
-/**
- * Listens for incoming RFCOMM connection for the headset / handsfree service.
- *
- * TODO: Use the new generic BluetoothSocket class instead of this legacy code
- *
- * @hide
- */
-public final class BluetoothAudioGateway {
- private static final String TAG = "BT Audio Gateway";
- private static final boolean DBG = false;
-
- private int mNativeData;
- static { classInitNative(); }
-
- /* in */
- private int mHandsfreeAgRfcommChannel = -1;
- private int mHeadsetAgRfcommChannel = -1;
-
- /* out - written by native code */
- private String mConnectingHeadsetAddress;
- private int mConnectingHeadsetRfcommChannel; /* -1 when not connected */
- private int mConnectingHeadsetSocketFd;
- private String mConnectingHandsfreeAddress;
- private int mConnectingHandsfreeRfcommChannel; /* -1 when not connected */
- private int mConnectingHandsfreeSocketFd;
- private int mTimeoutRemainingMs; /* in/out */
-
- private final BluetoothAdapter mAdapter;
-
- public static final int DEFAULT_HF_AG_CHANNEL = 10;
- public static final int DEFAULT_HS_AG_CHANNEL = 11;
-
- public BluetoothAudioGateway(BluetoothAdapter adapter) {
- this(adapter, DEFAULT_HF_AG_CHANNEL, DEFAULT_HS_AG_CHANNEL);
- }
-
- public BluetoothAudioGateway(BluetoothAdapter adapter, int handsfreeAgRfcommChannel,
- int headsetAgRfcommChannel) {
- mAdapter = adapter;
- mHandsfreeAgRfcommChannel = handsfreeAgRfcommChannel;
- mHeadsetAgRfcommChannel = headsetAgRfcommChannel;
- initializeNativeDataNative();
- }
-
- private Thread mConnectThead;
- private volatile boolean mInterrupted;
- private static final int SELECT_WAIT_TIMEOUT = 1000;
-
- private Handler mCallback;
-
- public class IncomingConnectionInfo {
- public BluetoothAdapter mAdapter;
- public BluetoothDevice mRemoteDevice;
- public int mSocketFd;
- public int mRfcommChan;
- IncomingConnectionInfo(BluetoothAdapter adapter, BluetoothDevice remoteDevice,
- int socketFd, int rfcommChan) {
- mAdapter = adapter;
- mRemoteDevice = remoteDevice;
- mSocketFd = socketFd;
- mRfcommChan = rfcommChan;
- }
- }
-
- public static final int MSG_INCOMING_HEADSET_CONNECTION = 100;
- public static final int MSG_INCOMING_HANDSFREE_CONNECTION = 101;
-
- public synchronized boolean start(Handler callback) {
-
- if (mConnectThead == null) {
- mCallback = callback;
- mConnectThead = new Thread(TAG) {
- public void run() {
- if (DBG) log("Connect Thread starting");
- while (!mInterrupted) {
- //Log.i(TAG, "waiting for connect");
- mConnectingHeadsetRfcommChannel = -1;
- mConnectingHandsfreeRfcommChannel = -1;
- if (waitForHandsfreeConnectNative(SELECT_WAIT_TIMEOUT) == false) {
- if (mTimeoutRemainingMs > 0) {
- try {
- Log.i(TAG, "select thread timed out, but " +
- mTimeoutRemainingMs + "ms of waiting remain.");
- Thread.sleep(mTimeoutRemainingMs);
- } catch (InterruptedException e) {
- Log.i(TAG, "select thread was interrupted (2), exiting");
- mInterrupted = true;
- }
- }
- }
- else {
- Log.i(TAG, "connect notification!");
- /* A device connected (most likely just one, but
- it is possible for two separate devices, one
- a headset and one a handsfree, to connect
- simultaneously.
- */
- if (mConnectingHeadsetRfcommChannel >= 0) {
- Log.i(TAG, "Incoming connection from headset " +
- mConnectingHeadsetAddress + " on channel " +
- mConnectingHeadsetRfcommChannel);
- Message msg = Message.obtain(mCallback);
- msg.what = MSG_INCOMING_HEADSET_CONNECTION;
- msg.obj = new IncomingConnectionInfo(
- mAdapter,
- mAdapter.getRemoteDevice(mConnectingHeadsetAddress),
- mConnectingHeadsetSocketFd,
- mConnectingHeadsetRfcommChannel);
- msg.sendToTarget();
- }
- if (mConnectingHandsfreeRfcommChannel >= 0) {
- Log.i(TAG, "Incoming connection from handsfree " +
- mConnectingHandsfreeAddress + " on channel " +
- mConnectingHandsfreeRfcommChannel);
- Message msg = Message.obtain();
- msg.setTarget(mCallback);
- msg.what = MSG_INCOMING_HANDSFREE_CONNECTION;
- msg.obj = new IncomingConnectionInfo(
- mAdapter,
- mAdapter.getRemoteDevice(mConnectingHandsfreeAddress),
- mConnectingHandsfreeSocketFd,
- mConnectingHandsfreeRfcommChannel);
- msg.sendToTarget();
- }
- }
- }
- if (DBG) log("Connect Thread finished");
- }
- };
-
- if (setUpListeningSocketsNative() == false) {
- Log.e(TAG, "Could not set up listening socket, exiting");
- return false;
- }
-
- mInterrupted = false;
- mConnectThead.start();
- }
-
- return true;
- }
-
- public synchronized void stop() {
- if (mConnectThead != null) {
- if (DBG) log("stopping Connect Thread");
- mInterrupted = true;
- try {
- mConnectThead.interrupt();
- if (DBG) log("waiting for thread to terminate");
- mConnectThead.join();
- mConnectThead = null;
- mCallback = null;
- tearDownListeningSocketsNative();
- } catch (InterruptedException e) {
- Log.w(TAG, "Interrupted waiting for Connect Thread to join");
- }
- }
- }
-
- protected void finalize() throws Throwable {
- try {
- cleanupNativeDataNative();
- } finally {
- super.finalize();
- }
- }
-
- private static native void classInitNative();
- private native void initializeNativeDataNative();
- private native void cleanupNativeDataNative();
- private native boolean waitForHandsfreeConnectNative(int timeoutMs);
- private native boolean setUpListeningSocketsNative();
- private native void tearDownListeningSocketsNative();
-
- private static void log(String msg) {
- Log.d(TAG, msg);
- }
-}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 56e1735..4cc22b4 100644..100755
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -65,6 +65,7 @@ import java.util.UUID;
*/
public final class BluetoothDevice implements Parcelable {
private static final String TAG = "BluetoothDevice";
+ private static final boolean DBG = false;
/**
* Sentinel error value for this class. Guaranteed to not equal any other
@@ -483,16 +484,29 @@ public final class BluetoothDevice implements Parcelable {
/*package*/ static IBluetooth getService() {
synchronized (BluetoothDevice.class) {
if (sService == null) {
- IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
- if (b == null) {
- throw new RuntimeException("Bluetooth service not available");
- }
- sService = IBluetooth.Stub.asInterface(b);
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ sService = adapter.getBluetoothService(mStateChangeCallback);
}
}
return sService;
}
+ static IBluetoothManagerCallback mStateChangeCallback = new IBluetoothManagerCallback.Stub() {
+
+ public void onBluetoothServiceUp(IBluetooth bluetoothService)
+ throws RemoteException {
+ synchronized (BluetoothDevice.class) {
+ sService = bluetoothService;
+ }
+ }
+
+ public void onBluetoothServiceDown()
+ throws RemoteException {
+ synchronized (BluetoothDevice.class) {
+ sService = null;
+ }
+ }
+ };
/**
* Create a new BluetoothDevice
* Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB",
@@ -561,6 +575,7 @@ public final class BluetoothDevice implements Parcelable {
* @return Bluetooth hardware address as string
*/
public String getAddress() {
+ if (DBG) Log.d(TAG, "mAddress: " + mAddress);
return mAddress;
}
@@ -575,8 +590,12 @@ public final class BluetoothDevice implements Parcelable {
* @return the Bluetooth name, or null if there was a problem.
*/
public String getName() {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot get Remote Device name");
+ return null;
+ }
try {
- return sService.getRemoteName(mAddress);
+ return sService.getRemoteName(this);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return null;
}
@@ -589,8 +608,12 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
public String getAlias() {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot get Remote Device Alias");
+ return null;
+ }
try {
- return sService.getRemoteAlias(mAddress);
+ return sService.getRemoteAlias(this);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return null;
}
@@ -606,8 +629,12 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
public boolean setAlias(String alias) {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot set Remote Device name");
+ return false;
+ }
try {
- return sService.setRemoteAlias(mAddress, alias);
+ return sService.setRemoteAlias(this, alias);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -642,8 +669,12 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
public boolean createBond() {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot create bond to Remote Device");
+ return false;
+ }
try {
- return sService.createBond(mAddress);
+ return sService.createBond(this);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -668,9 +699,11 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
public boolean createBondOutOfBand(byte[] hash, byte[] randomizer) {
+ //TODO(BT)
+ /*
try {
- return sService.createBondOutOfBand(mAddress, hash, randomizer);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return sService.createBondOutOfBand(this, hash, randomizer);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}*/
return false;
}
@@ -688,9 +721,11 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
public boolean setDeviceOutOfBandData(byte[] hash, byte[] randomizer) {
+ //TODO(BT)
+ /*
try {
- return sService.setDeviceOutOfBandData(mAddress, hash, randomizer);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return sService.setDeviceOutOfBandData(this, hash, randomizer);
+ } catch (RemoteException e) {Log.e(TAG, "", e);} */
return false;
}
@@ -702,8 +737,12 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
public boolean cancelBondProcess() {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot cancel Remote Device bond");
+ return false;
+ }
try {
- return sService.cancelBondProcess(mAddress);
+ return sService.cancelBondProcess(this);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -719,8 +758,12 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
public boolean removeBond() {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot remove Remote Device bond");
+ return false;
+ }
try {
- return sService.removeBond(mAddress);
+ return sService.removeBond(this);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
@@ -736,9 +779,19 @@ public final class BluetoothDevice implements Parcelable {
* @return the bond state
*/
public int getBondState() {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot get bond state");
+ return BOND_NONE;
+ }
try {
- return sService.getBondState(mAddress);
+ return sService.getBondState(this);
} catch (RemoteException e) {Log.e(TAG, "", e);}
+ catch (NullPointerException npe) {
+ // Handle case where bluetooth service proxy
+ // is already null.
+ Log.e(TAG, "NullPointerException for getBondState() of device ("+
+ getAddress()+")", npe);
+ }
return BOND_NONE;
}
@@ -749,8 +802,12 @@ public final class BluetoothDevice implements Parcelable {
* @return Bluetooth class object, or null on error
*/
public BluetoothClass getBluetoothClass() {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot get Bluetooth Class");
+ return null;
+ }
try {
- int classInt = sService.getRemoteClass(mAddress);
+ int classInt = sService.getRemoteClass(this);
if (classInt == BluetoothClass.ERROR) return null;
return new BluetoothClass(classInt);
} catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -763,11 +820,13 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
public boolean getTrustState() {
+ //TODO(BT)
+ /*
try {
- return sService.getTrustState(mAddress);
+ return sService.getTrustState(this);
} catch (RemoteException e) {
Log.e(TAG, "", e);
- }
+ }*/
return false;
}
@@ -778,11 +837,13 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
public boolean setTrust(boolean value) {
+ //TODO(BT)
+ /*
try {
- return sService.setTrust(mAddress, value);
+ return sService.setTrust(this, value);
} catch (RemoteException e) {
Log.e(TAG, "", e);
- }
+ }*/
return false;
}
@@ -799,8 +860,12 @@ public final class BluetoothDevice implements Parcelable {
* or null on error
*/
public ParcelUuid[] getUuids() {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot get remote device Uuids");
+ return null;
+ }
try {
- return sService.getRemoteUuids(mAddress);
+ return sService.getRemoteUuids(this);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return null;
}
@@ -822,64 +887,84 @@ public final class BluetoothDevice implements Parcelable {
*/
public boolean fetchUuidsWithSdp() {
try {
- return sService.fetchRemoteUuids(mAddress, null, null);
+ return sService.fetchRemoteUuids(this);
} catch (RemoteException e) {Log.e(TAG, "", e);}
- return false;
+ return false;
}
/** @hide */
public int getServiceChannel(ParcelUuid uuid) {
+ //TODO(BT)
+ /*
try {
- return sService.getRemoteServiceChannel(mAddress, uuid);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return sService.getRemoteServiceChannel(this, uuid);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}*/
return BluetoothDevice.ERROR;
}
/** @hide */
public boolean setPin(byte[] pin) {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot set Remote Device pin");
+ return false;
+ }
try {
- return sService.setPin(mAddress, pin);
+ return sService.setPin(this, true, pin.length, pin);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
/** @hide */
public boolean setPasskey(int passkey) {
+ //TODO(BT)
+ /*
try {
- return sService.setPasskey(mAddress, passkey);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return sService.setPasskey(this, true, 4, passkey);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}*/
return false;
}
/** @hide */
public boolean setPairingConfirmation(boolean confirm) {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot set pairing confirmation");
+ return false;
+ }
try {
- return sService.setPairingConfirmation(mAddress, confirm);
+ return sService.setPairingConfirmation(this, confirm);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
/** @hide */
public boolean setRemoteOutOfBandData() {
+ // TODO(BT)
+ /*
try {
- return sService.setRemoteOutOfBandData(mAddress);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return sService.setRemoteOutOfBandData(this);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}*/
return false;
}
/** @hide */
public boolean cancelPairingUserInput() {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot create pairing user input");
+ return false;
+ }
try {
- return sService.cancelPairingUserInput(mAddress);
+ return sService.cancelBondProcess(this);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
/** @hide */
public boolean isBluetoothDock() {
+ // TODO(BT)
+ /*
try {
- return sService.isBluetoothDock(mAddress);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return sService.isBluetoothDock(this);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}*/
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
deleted file mode 100644
index 020f051..0000000
--- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java
+++ /dev/null
@@ -1,1357 +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 android.bluetooth;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Message;
-import android.bluetooth.BluetoothAdapter;
-import android.os.PowerManager;
-import android.server.BluetoothA2dpService;
-import android.server.BluetoothService;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-
-import java.util.Set;
-
-/**
- * This class is the Profile connection state machine associated with a remote
- * device. When the device bonds an instance of this class is created.
- * This tracks incoming and outgoing connections of all the profiles. Incoming
- * connections are preferred over outgoing connections and HFP preferred over
- * A2DP. When the device is unbonded, the instance is removed.
- *
- * States:
- * {@link BondedDevice}: This state represents a bonded device. When in this
- * state none of the profiles are in transition states.
- *
- * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition
- * state because of a outgoing Connect or Disconnect.
- *
- * {@link IncomingHandsfree}: Handsfree profile connection is in a transition
- * state because of a incoming Connect or Disconnect.
- *
- * {@link IncomingA2dp}: A2dp profile connection is in a transition
- * state because of a incoming Connect or Disconnect.
- *
- * {@link OutgoingA2dp}: A2dp profile connection is in a transition
- * state because of a outgoing Connect or Disconnect.
- *
- * Todo(): Write tests for this class, when the Android Mock support is completed.
- * @hide
- */
-public final class BluetoothDeviceProfileState extends StateMachine {
- private static final String TAG = "BluetoothDeviceProfileState";
- private static final boolean DBG = false;
-
- // TODO(): Restructure the state machine to make it scalable with regard to profiles.
- public static final int CONNECT_HFP_OUTGOING = 1;
- public static final int CONNECT_HFP_INCOMING = 2;
- public static final int CONNECT_A2DP_OUTGOING = 3;
- public static final int CONNECT_A2DP_INCOMING = 4;
- public static final int CONNECT_HID_OUTGOING = 5;
- public static final int CONNECT_HID_INCOMING = 6;
-
- public static final int DISCONNECT_HFP_OUTGOING = 50;
- private static final int DISCONNECT_HFP_INCOMING = 51;
- public static final int DISCONNECT_A2DP_OUTGOING = 52;
- public static final int DISCONNECT_A2DP_INCOMING = 53;
- public static final int DISCONNECT_HID_OUTGOING = 54;
- public static final int DISCONNECT_HID_INCOMING = 55;
- public static final int DISCONNECT_PBAP_OUTGOING = 56;
-
- public static final int UNPAIR = 100;
- public static final int AUTO_CONNECT_PROFILES = 101;
- public static final int TRANSITION_TO_STABLE = 102;
- public static final int CONNECT_OTHER_PROFILES = 103;
- private static final int CONNECTION_ACCESS_REQUEST_REPLY = 104;
- private static final int CONNECTION_ACCESS_REQUEST_EXPIRY = 105;
-
- public static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs
- private static final int CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT = 7000; // 7 secs
- private static final int CONNECTION_ACCESS_UNDEFINED = -1;
- private static final long INIT_INCOMING_REJECT_TIMER = 1000; // 1 sec
- private static final long MAX_INCOMING_REJECT_TIMER = 3600 * 1000 * 4; // 4 hours
-
- private static final String ACCESS_AUTHORITY_PACKAGE = "com.android.settings";
- private static final String ACCESS_AUTHORITY_CLASS =
- "com.android.settings.bluetooth.BluetoothPermissionRequest";
-
- private BondedDevice mBondedDevice = new BondedDevice();
- private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree();
- private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
- private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
- private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
- private OutgoingHid mOutgoingHid = new OutgoingHid();
- private IncomingHid mIncomingHid = new IncomingHid();
-
- private Context mContext;
- private BluetoothService mService;
- private BluetoothA2dpService mA2dpService;
- private BluetoothHeadset mHeadsetService;
- private BluetoothPbap mPbapService;
- private PbapServiceListener mPbap;
- private BluetoothAdapter mAdapter;
- private boolean mPbapServiceConnected;
- private boolean mAutoConnectionPending;
- private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
-
- private BluetoothDevice mDevice;
- private int mHeadsetState = BluetoothProfile.STATE_DISCONNECTED;
- private int mA2dpState = BluetoothProfile.STATE_DISCONNECTED;
- private long mIncomingRejectTimer;
- private boolean mConnectionAccessReplyReceived = false;
- private Pair<Integer, String> mIncomingConnections;
- private PowerManager.WakeLock mWakeLock;
- private PowerManager mPowerManager;
- private boolean mPairingRequestRcvd = false;
-
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (device == null || !device.equals(mDevice)) return;
-
- if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
- int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
- int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
- // We trust this device now
- if (newState == BluetoothHeadset.STATE_CONNECTED) {
- setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
- }
- mA2dpState = newState;
- if (oldState == BluetoothA2dp.STATE_CONNECTED &&
- newState == BluetoothA2dp.STATE_DISCONNECTED) {
- sendMessage(DISCONNECT_A2DP_INCOMING);
- }
- if (newState == BluetoothProfile.STATE_CONNECTED ||
- newState == BluetoothProfile.STATE_DISCONNECTED) {
- sendMessage(TRANSITION_TO_STABLE);
- }
- } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
- int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
- int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
- // We trust this device now
- if (newState == BluetoothHeadset.STATE_CONNECTED) {
- setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
- }
- mHeadsetState = newState;
- if (oldState == BluetoothHeadset.STATE_CONNECTED &&
- newState == BluetoothHeadset.STATE_DISCONNECTED) {
- sendMessage(DISCONNECT_HFP_INCOMING);
- }
- if (newState == BluetoothProfile.STATE_CONNECTED ||
- newState == BluetoothProfile.STATE_DISCONNECTED) {
- sendMessage(TRANSITION_TO_STABLE);
- }
- } else if (action.equals(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED)) {
- int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
- int oldState =
- intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
- // We trust this device now
- if (newState == BluetoothHeadset.STATE_CONNECTED) {
- setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
- }
- if (oldState == BluetoothProfile.STATE_CONNECTED &&
- newState == BluetoothProfile.STATE_DISCONNECTED) {
- sendMessage(DISCONNECT_HID_INCOMING);
- }
- if (newState == BluetoothProfile.STATE_CONNECTED ||
- newState == BluetoothProfile.STATE_DISCONNECTED) {
- sendMessage(TRANSITION_TO_STABLE);
- }
- } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
- // This is technically not needed, but we can get stuck sometimes.
- // For example, if incoming A2DP fails, we are not informed by Bluez
- sendMessage(TRANSITION_TO_STABLE);
- } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) {
- mWakeLock.release();
- int val = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
- BluetoothDevice.CONNECTION_ACCESS_NO);
- Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_REPLY);
- msg.arg1 = val;
- sendMessage(msg);
- } else if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
- mPairingRequestRcvd = true;
- } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
- int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
- BluetoothDevice.ERROR);
- if (state == BluetoothDevice.BOND_BONDED && mPairingRequestRcvd) {
- setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
- mPairingRequestRcvd = false;
- } else if (state == BluetoothDevice.BOND_NONE) {
- mPairingRequestRcvd = false;
- }
- }
- }
- };
-
- private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) {
- // This works only because these broadcast intents are "sticky"
- Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
- if (i != null) {
- int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
- if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (device != null && autoConnectDevice.equals(device)) {
- return true;
- }
- }
- }
- return false;
- }
-
- public BluetoothDeviceProfileState(Context context, String address,
- BluetoothService service, BluetoothA2dpService a2dpService, boolean setTrust) {
- super(address);
- mContext = context;
- mDevice = new BluetoothDevice(address);
- mService = service;
- mA2dpService = a2dpService;
-
- addState(mBondedDevice);
- addState(mOutgoingHandsfree);
- addState(mIncomingHandsfree);
- addState(mIncomingA2dp);
- addState(mOutgoingA2dp);
- addState(mOutgoingHid);
- addState(mIncomingHid);
- setInitialState(mBondedDevice);
-
- IntentFilter filter = new IntentFilter();
- // Fine-grained state broadcasts
- filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
- filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
- filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
- filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
- filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
-
- mContext.registerReceiver(mBroadcastReceiver, filter);
-
- mAdapter = BluetoothAdapter.getDefaultAdapter();
- mAdapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
- BluetoothProfile.HEADSET);
- // TODO(): Convert PBAP to the new Profile APIs.
- mPbap = new PbapServiceListener();
-
- mIncomingConnections = mService.getIncomingState(address);
- mIncomingRejectTimer = readTimerValue();
- mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
- mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK |
- PowerManager.ACQUIRE_CAUSES_WAKEUP |
- PowerManager.ON_AFTER_RELEASE, TAG);
- mWakeLock.setReferenceCounted(false);
-
- if (setTrust) {
- setTrust(BluetoothDevice.CONNECTION_ACCESS_YES);
- }
- }
-
- private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
- new BluetoothProfile.ServiceListener() {
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- synchronized(BluetoothDeviceProfileState.this) {
- mHeadsetService = (BluetoothHeadset) proxy;
- if (mAutoConnectionPending) {
- sendMessage(AUTO_CONNECT_PROFILES);
- mAutoConnectionPending = false;
- }
- }
- }
- public void onServiceDisconnected(int profile) {
- synchronized(BluetoothDeviceProfileState.this) {
- mHeadsetService = null;
- }
- }
- };
-
- private class PbapServiceListener implements BluetoothPbap.ServiceListener {
- public PbapServiceListener() {
- mPbapService = new BluetoothPbap(mContext, this);
- }
- public void onServiceConnected() {
- synchronized(BluetoothDeviceProfileState.this) {
- mPbapServiceConnected = true;
- }
- }
- public void onServiceDisconnected() {
- synchronized(BluetoothDeviceProfileState.this) {
- mPbapServiceConnected = false;
- }
- }
- }
-
- @Override
- protected void onQuitting() {
- mContext.unregisterReceiver(mBroadcastReceiver);
- mBroadcastReceiver = null;
- mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadsetService);
- mBluetoothProfileServiceListener = null;
- mOutgoingHandsfree = null;
- mPbap = null;
- mPbapService.close();
- mPbapService = null;
- mIncomingHid = null;
- mOutgoingHid = null;
- mIncomingHandsfree = null;
- mOutgoingHandsfree = null;
- mIncomingA2dp = null;
- mOutgoingA2dp = null;
- mBondedDevice = null;
- }
-
- private class BondedDevice extends State {
- @Override
- public void enter() {
- Log.i(TAG, "Entering ACL Connected state with: " + getCurrentMessage().what);
- Message m = new Message();
- m.copyFrom(getCurrentMessage());
- sendMessageAtFrontOfQueue(m);
- }
- @Override
- public boolean processMessage(Message message) {
- log("ACL Connected State -> Processing Message: " + message.what);
- switch(message.what) {
- case CONNECT_HFP_OUTGOING:
- case DISCONNECT_HFP_OUTGOING:
- transitionTo(mOutgoingHandsfree);
- break;
- case CONNECT_HFP_INCOMING:
- transitionTo(mIncomingHandsfree);
- break;
- case DISCONNECT_HFP_INCOMING:
- transitionTo(mIncomingHandsfree);
- break;
- case CONNECT_A2DP_OUTGOING:
- case DISCONNECT_A2DP_OUTGOING:
- transitionTo(mOutgoingA2dp);
- break;
- case CONNECT_A2DP_INCOMING:
- case DISCONNECT_A2DP_INCOMING:
- transitionTo(mIncomingA2dp);
- break;
- case CONNECT_HID_OUTGOING:
- case DISCONNECT_HID_OUTGOING:
- transitionTo(mOutgoingHid);
- break;
- case CONNECT_HID_INCOMING:
- case DISCONNECT_HID_INCOMING:
- transitionTo(mIncomingHid);
- break;
- case DISCONNECT_PBAP_OUTGOING:
- processCommand(DISCONNECT_PBAP_OUTGOING);
- break;
- case UNPAIR:
- if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
- sendMessage(DISCONNECT_HFP_OUTGOING);
- deferMessage(message);
- break;
- } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) {
- sendMessage(DISCONNECT_A2DP_OUTGOING);
- deferMessage(message);
- break;
- } else if (mService.getInputDeviceConnectionState(mDevice) !=
- BluetoothInputDevice.STATE_DISCONNECTED) {
- sendMessage(DISCONNECT_HID_OUTGOING);
- deferMessage(message);
- break;
- }
- processCommand(UNPAIR);
- break;
- case AUTO_CONNECT_PROFILES:
- if (isPhoneDocked(mDevice)) {
- // Don't auto connect to docks.
- break;
- } else {
- if (mHeadsetService == null) {
- mAutoConnectionPending = true;
- } else if (mHeadsetService.getPriority(mDevice) ==
- BluetoothHeadset.PRIORITY_AUTO_CONNECT &&
- mHeadsetService.getDevicesMatchingConnectionStates(
- new int[] {BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTING}).size() == 0) {
- mHeadsetService.connect(mDevice);
- }
- if (mA2dpService != null &&
- mA2dpService.getPriority(mDevice) ==
- BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
- mA2dpService.getDevicesMatchingConnectionStates(
- new int[] {BluetoothA2dp.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTING}).size() == 0) {
- mA2dpService.connect(mDevice);
- }
- if (mService.getInputDevicePriority(mDevice) ==
- BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
- mService.connectInputDevice(mDevice);
- }
- }
- break;
- case CONNECT_OTHER_PROFILES:
- if (isPhoneDocked(mDevice)) {
- break;
- }
- if (message.arg1 == CONNECT_A2DP_OUTGOING) {
- if (mA2dpService != null &&
- mA2dpService.getConnectedDevices().size() == 0) {
- Log.i(TAG, "A2dp:Connect Other Profiles");
- mA2dpService.connect(mDevice);
- }
- } else if (message.arg1 == CONNECT_HFP_OUTGOING) {
- if (mHeadsetService == null) {
- deferMessage(message);
- } else {
- if (mHeadsetService.getConnectedDevices().size() == 0) {
- Log.i(TAG, "Headset:Connect Other Profiles");
- mHeadsetService.connect(mDevice);
- }
- }
- }
- break;
- case TRANSITION_TO_STABLE:
- // ignore.
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
- private class OutgoingHandsfree extends State {
- private boolean mStatus = false;
- private int mCommand;
-
- @Override
- public void enter() {
- Log.i(TAG, "Entering OutgoingHandsfree state with: " + getCurrentMessage().what);
- mCommand = getCurrentMessage().what;
- if (mCommand != CONNECT_HFP_OUTGOING &&
- mCommand != DISCONNECT_HFP_OUTGOING) {
- Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand);
- }
- mStatus = processCommand(mCommand);
- if (!mStatus) {
- sendMessage(TRANSITION_TO_STABLE);
- mService.sendProfileStateMessage(BluetoothProfileState.HFP,
- BluetoothProfileState.TRANSITION_TO_STABLE);
- }
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("OutgoingHandsfree State -> Processing Message: " + message.what);
- Message deferMsg = new Message();
- int command = message.what;
- switch(command) {
- case CONNECT_HFP_OUTGOING:
- if (command != mCommand) {
- // Disconnect followed by a connect - defer
- deferMessage(message);
- }
- break;
- case CONNECT_HFP_INCOMING:
- if (mCommand == CONNECT_HFP_OUTGOING) {
- // Cancel outgoing connect, accept incoming
- cancelCommand(CONNECT_HFP_OUTGOING);
- transitionTo(mIncomingHandsfree);
- } else {
- // We have done the disconnect but we are not
- // sure which state we are in at this point.
- deferMessage(message);
- }
- break;
- case CONNECT_A2DP_INCOMING:
- // accept incoming A2DP, retry HFP_OUTGOING
- transitionTo(mIncomingA2dp);
-
- if (mStatus) {
- deferMsg.what = mCommand;
- deferMessage(deferMsg);
- }
- break;
- case CONNECT_A2DP_OUTGOING:
- deferMessage(message);
- break;
- case DISCONNECT_HFP_OUTGOING:
- if (mCommand == CONNECT_HFP_OUTGOING) {
- // Cancel outgoing connect
- cancelCommand(CONNECT_HFP_OUTGOING);
- processCommand(DISCONNECT_HFP_OUTGOING);
- }
- // else ignore
- break;
- case DISCONNECT_HFP_INCOMING:
- // When this happens the socket would be closed and the headset
- // state moved to DISCONNECTED, cancel the outgoing thread.
- // if it still is in CONNECTING state
- cancelCommand(CONNECT_HFP_OUTGOING);
- break;
- case DISCONNECT_A2DP_OUTGOING:
- deferMessage(message);
- break;
- case DISCONNECT_A2DP_INCOMING:
- // Bluez will handle the disconnect. If because of this the outgoing
- // handsfree connection has failed, then retry.
- if (mStatus) {
- deferMsg.what = mCommand;
- deferMessage(deferMsg);
- }
- break;
- case CONNECT_HID_OUTGOING:
- case DISCONNECT_HID_OUTGOING:
- deferMessage(message);
- break;
- case CONNECT_HID_INCOMING:
- transitionTo(mIncomingHid);
- if (mStatus) {
- deferMsg.what = mCommand;
- deferMessage(deferMsg);
- }
- break;
- case DISCONNECT_HID_INCOMING:
- if (mStatus) {
- deferMsg.what = mCommand;
- deferMessage(deferMsg);
- }
- break; // ignore
- case DISCONNECT_PBAP_OUTGOING:
- case UNPAIR:
- case AUTO_CONNECT_PROFILES:
- case CONNECT_OTHER_PROFILES:
- deferMessage(message);
- break;
- case TRANSITION_TO_STABLE:
- transitionTo(mBondedDevice);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
- private class IncomingHandsfree extends State {
- private boolean mStatus = false;
- private int mCommand;
-
- @Override
- public void enter() {
- Log.i(TAG, "Entering IncomingHandsfree state with: " + getCurrentMessage().what);
- mCommand = getCurrentMessage().what;
- if (mCommand != CONNECT_HFP_INCOMING &&
- mCommand != DISCONNECT_HFP_INCOMING) {
- Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand);
- }
- mStatus = processCommand(mCommand);
- if (!mStatus) {
- sendMessage(TRANSITION_TO_STABLE);
- mService.sendProfileStateMessage(BluetoothProfileState.HFP,
- BluetoothProfileState.TRANSITION_TO_STABLE);
- }
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("IncomingHandsfree State -> Processing Message: " + message.what);
- switch(message.what) {
- case CONNECT_HFP_OUTGOING:
- deferMessage(message);
- break;
- case CONNECT_HFP_INCOMING:
- // Ignore
- Log.e(TAG, "Error: Incoming connection with a pending incoming connection");
- break;
- case CONNECTION_ACCESS_REQUEST_REPLY:
- int val = message.arg1;
- mConnectionAccessReplyReceived = true;
- boolean value = false;
- if (val == BluetoothDevice.CONNECTION_ACCESS_YES) {
- value = true;
- }
- setTrust(val);
-
- handleIncomingConnection(CONNECT_HFP_INCOMING, value);
- break;
- case CONNECTION_ACCESS_REQUEST_EXPIRY:
- if (!mConnectionAccessReplyReceived) {
- handleIncomingConnection(CONNECT_HFP_INCOMING, false);
- sendConnectionAccessRemovalIntent();
- sendMessage(TRANSITION_TO_STABLE);
- }
- break;
- case CONNECT_A2DP_INCOMING:
- // Serialize the commands.
- deferMessage(message);
- break;
- case CONNECT_A2DP_OUTGOING:
- deferMessage(message);
- break;
- case DISCONNECT_HFP_OUTGOING:
- // We don't know at what state we are in the incoming HFP connection state.
- // We can be changing from DISCONNECTED to CONNECTING, or
- // from CONNECTING to CONNECTED, so serializing this command is
- // the safest option.
- deferMessage(message);
- break;
- case DISCONNECT_HFP_INCOMING:
- // Nothing to do here, we will already be DISCONNECTED
- // by this point.
- break;
- case DISCONNECT_A2DP_OUTGOING:
- deferMessage(message);
- break;
- case DISCONNECT_A2DP_INCOMING:
- // Bluez handles incoming A2DP disconnect.
- // If this causes incoming HFP to fail, it is more of a headset problem
- // since both connections are incoming ones.
- break;
- case CONNECT_HID_OUTGOING:
- case DISCONNECT_HID_OUTGOING:
- deferMessage(message);
- break;
- case CONNECT_HID_INCOMING:
- case DISCONNECT_HID_INCOMING:
- break; // ignore
- case DISCONNECT_PBAP_OUTGOING:
- case UNPAIR:
- case AUTO_CONNECT_PROFILES:
- case CONNECT_OTHER_PROFILES:
- deferMessage(message);
- break;
- case TRANSITION_TO_STABLE:
- transitionTo(mBondedDevice);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
- private class OutgoingA2dp extends State {
- private boolean mStatus = false;
- private int mCommand;
-
- @Override
- public void enter() {
- Log.i(TAG, "Entering OutgoingA2dp state with: " + getCurrentMessage().what);
- mCommand = getCurrentMessage().what;
- if (mCommand != CONNECT_A2DP_OUTGOING &&
- mCommand != DISCONNECT_A2DP_OUTGOING) {
- Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand);
- }
- mStatus = processCommand(mCommand);
- if (!mStatus) {
- sendMessage(TRANSITION_TO_STABLE);
- mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
- BluetoothProfileState.TRANSITION_TO_STABLE);
- }
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("OutgoingA2dp State->Processing Message: " + message.what);
- Message deferMsg = new Message();
- switch(message.what) {
- case CONNECT_HFP_OUTGOING:
- processCommand(CONNECT_HFP_OUTGOING);
-
- // Don't cancel A2DP outgoing as there is no guarantee it
- // will get canceled.
- // It might already be connected but we might not have got the
- // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here.
- // The worst case, the connection will fail, retry.
- // The same applies to Disconnecting an A2DP connection.
- if (mStatus) {
- deferMsg.what = mCommand;
- deferMessage(deferMsg);
- }
- break;
- case CONNECT_HFP_INCOMING:
- processCommand(CONNECT_HFP_INCOMING);
-
- // Don't cancel A2DP outgoing as there is no guarantee
- // it will get canceled.
- // The worst case, the connection will fail, retry.
- if (mStatus) {
- deferMsg.what = mCommand;
- deferMessage(deferMsg);
- }
- break;
- case CONNECT_A2DP_INCOMING:
- // Bluez will take care of conflicts between incoming and outgoing
- // connections.
- transitionTo(mIncomingA2dp);
- break;
- case CONNECT_A2DP_OUTGOING:
- // Ignore
- break;
- case DISCONNECT_HFP_OUTGOING:
- deferMessage(message);
- break;
- case DISCONNECT_HFP_INCOMING:
- // At this point, we are already disconnected
- // with HFP. Sometimes A2DP connection can
- // fail due to the disconnection of HFP. So add a retry
- // for the A2DP.
- if (mStatus) {
- deferMsg.what = mCommand;
- deferMessage(deferMsg);
- }
- break;
- case DISCONNECT_A2DP_OUTGOING:
- deferMessage(message);
- break;
- case DISCONNECT_A2DP_INCOMING:
- // Ignore, will be handled by Bluez
- break;
- case CONNECT_HID_OUTGOING:
- case DISCONNECT_HID_OUTGOING:
- deferMessage(message);
- break;
- case CONNECT_HID_INCOMING:
- transitionTo(mIncomingHid);
- if (mStatus) {
- deferMsg.what = mCommand;
- deferMessage(deferMsg);
- }
- break;
- case DISCONNECT_HID_INCOMING:
- if (mStatus) {
- deferMsg.what = mCommand;
- deferMessage(deferMsg);
- }
- break; // ignore
- case DISCONNECT_PBAP_OUTGOING:
- case UNPAIR:
- case AUTO_CONNECT_PROFILES:
- case CONNECT_OTHER_PROFILES:
- deferMessage(message);
- break;
- case TRANSITION_TO_STABLE:
- transitionTo(mBondedDevice);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
- private class IncomingA2dp extends State {
- private boolean mStatus = false;
- private int mCommand;
-
- @Override
- public void enter() {
- Log.i(TAG, "Entering IncomingA2dp state with: " + getCurrentMessage().what);
- mCommand = getCurrentMessage().what;
- if (mCommand != CONNECT_A2DP_INCOMING &&
- mCommand != DISCONNECT_A2DP_INCOMING) {
- Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand);
- }
- mStatus = processCommand(mCommand);
- if (!mStatus) {
- sendMessage(TRANSITION_TO_STABLE);
- mService.sendProfileStateMessage(BluetoothProfileState.A2DP,
- BluetoothProfileState.TRANSITION_TO_STABLE);
- }
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("IncomingA2dp State->Processing Message: " + message.what);
- switch(message.what) {
- case CONNECT_HFP_OUTGOING:
- deferMessage(message);
- break;
- case CONNECT_HFP_INCOMING:
- // Shouldn't happen, but serialize the commands.
- deferMessage(message);
- break;
- case CONNECT_A2DP_INCOMING:
- // ignore
- break;
- case CONNECTION_ACCESS_REQUEST_REPLY:
- int val = message.arg1;
- mConnectionAccessReplyReceived = true;
- boolean value = false;
- if (val == BluetoothDevice.CONNECTION_ACCESS_YES) {
- value = true;
- }
- setTrust(val);
- handleIncomingConnection(CONNECT_A2DP_INCOMING, value);
- break;
- case CONNECTION_ACCESS_REQUEST_EXPIRY:
- // The check protects the race condition between REQUEST_REPLY
- // and the timer expiry.
- if (!mConnectionAccessReplyReceived) {
- handleIncomingConnection(CONNECT_A2DP_INCOMING, false);
- sendConnectionAccessRemovalIntent();
- sendMessage(TRANSITION_TO_STABLE);
- }
- break;
- case CONNECT_A2DP_OUTGOING:
- // Defer message and retry
- deferMessage(message);
- break;
- case DISCONNECT_HFP_OUTGOING:
- deferMessage(message);
- break;
- case DISCONNECT_HFP_INCOMING:
- // Shouldn't happen but if does, we can handle it.
- // Depends if the headset can handle it.
- // Incoming A2DP will be handled by Bluez, Disconnect HFP
- // the socket would have already been closed.
- // ignore
- break;
- case DISCONNECT_A2DP_OUTGOING:
- deferMessage(message);
- break;
- case DISCONNECT_A2DP_INCOMING:
- // Ignore, will be handled by Bluez
- break;
- case CONNECT_HID_OUTGOING:
- case DISCONNECT_HID_OUTGOING:
- deferMessage(message);
- break;
- case CONNECT_HID_INCOMING:
- case DISCONNECT_HID_INCOMING:
- break; // ignore
- case DISCONNECT_PBAP_OUTGOING:
- case UNPAIR:
- case AUTO_CONNECT_PROFILES:
- case CONNECT_OTHER_PROFILES:
- deferMessage(message);
- break;
- case TRANSITION_TO_STABLE:
- transitionTo(mBondedDevice);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
-
- private class OutgoingHid extends State {
- private boolean mStatus = false;
- private int mCommand;
-
- @Override
- public void enter() {
- log("Entering OutgoingHid state with: " + getCurrentMessage().what);
- mCommand = getCurrentMessage().what;
- if (mCommand != CONNECT_HID_OUTGOING &&
- mCommand != DISCONNECT_HID_OUTGOING) {
- Log.e(TAG, "Error: OutgoingHid state with command:" + mCommand);
- }
- mStatus = processCommand(mCommand);
- if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("OutgoingHid State->Processing Message: " + message.what);
- Message deferMsg = new Message();
- switch(message.what) {
- // defer all outgoing messages
- case CONNECT_HFP_OUTGOING:
- case CONNECT_A2DP_OUTGOING:
- case CONNECT_HID_OUTGOING:
- case DISCONNECT_HFP_OUTGOING:
- case DISCONNECT_A2DP_OUTGOING:
- case DISCONNECT_HID_OUTGOING:
- deferMessage(message);
- break;
-
- case CONNECT_HFP_INCOMING:
- transitionTo(mIncomingHandsfree);
- break;
- case CONNECT_A2DP_INCOMING:
- transitionTo(mIncomingA2dp);
-
- // Don't cancel HID outgoing as there is no guarantee it
- // will get canceled.
- // It might already be connected but we might not have got the
- // INPUT_DEVICE_STATE_CHANGE. Hence, no point disconnecting here.
- // The worst case, the connection will fail, retry.
- if (mStatus) {
- deferMsg.what = mCommand;
- deferMessage(deferMsg);
- }
- break;
- case CONNECT_HID_INCOMING:
- // Bluez will take care of the conflicts
- transitionTo(mIncomingHid);
- break;
-
- case DISCONNECT_HFP_INCOMING:
- case DISCONNECT_A2DP_INCOMING:
- // At this point, we are already disconnected
- // with HFP. Sometimes HID connection can
- // fail due to the disconnection of HFP. So add a retry
- // for the HID.
- if (mStatus) {
- deferMsg.what = mCommand;
- deferMessage(deferMsg);
- }
- break;
- case DISCONNECT_HID_INCOMING:
- // Ignore, will be handled by Bluez
- break;
- case DISCONNECT_PBAP_OUTGOING:
- case UNPAIR:
- case AUTO_CONNECT_PROFILES:
- deferMessage(message);
- break;
- case TRANSITION_TO_STABLE:
- transitionTo(mBondedDevice);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
- private class IncomingHid extends State {
- private boolean mStatus = false;
- private int mCommand;
-
- @Override
- public void enter() {
- log("Entering IncomingHid state with: " + getCurrentMessage().what);
- mCommand = getCurrentMessage().what;
- if (mCommand != CONNECT_HID_INCOMING &&
- mCommand != DISCONNECT_HID_INCOMING) {
- Log.e(TAG, "Error: IncomingHid state with command:" + mCommand);
- }
- mStatus = processCommand(mCommand);
- if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("IncomingHid State->Processing Message: " + message.what);
- Message deferMsg = new Message();
- switch(message.what) {
- case CONNECT_HFP_OUTGOING:
- case CONNECT_HFP_INCOMING:
- case DISCONNECT_HFP_OUTGOING:
- case CONNECT_A2DP_INCOMING:
- case CONNECT_A2DP_OUTGOING:
- case DISCONNECT_A2DP_OUTGOING:
- case CONNECT_HID_OUTGOING:
- case CONNECT_HID_INCOMING:
- case DISCONNECT_HID_OUTGOING:
- deferMessage(message);
- break;
- case CONNECTION_ACCESS_REQUEST_REPLY:
- mConnectionAccessReplyReceived = true;
- int val = message.arg1;
- setTrust(val);
- handleIncomingConnection(CONNECT_HID_INCOMING,
- val == BluetoothDevice.CONNECTION_ACCESS_YES);
- break;
- case CONNECTION_ACCESS_REQUEST_EXPIRY:
- if (!mConnectionAccessReplyReceived) {
- handleIncomingConnection(CONNECT_HID_INCOMING, false);
- sendConnectionAccessRemovalIntent();
- sendMessage(TRANSITION_TO_STABLE);
- }
- break;
- case DISCONNECT_HFP_INCOMING:
- // Shouldn't happen but if does, we can handle it.
- // Depends if the headset can handle it.
- // Incoming HID will be handled by Bluez, Disconnect HFP
- // the socket would have already been closed.
- // ignore
- break;
- case DISCONNECT_HID_INCOMING:
- case DISCONNECT_A2DP_INCOMING:
- // Ignore, will be handled by Bluez
- break;
- case DISCONNECT_PBAP_OUTGOING:
- case UNPAIR:
- case AUTO_CONNECT_PROFILES:
- deferMessage(message);
- break;
- case TRANSITION_TO_STABLE:
- transitionTo(mBondedDevice);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
-
- synchronized void cancelCommand(int command) {
- if (command == CONNECT_HFP_OUTGOING ) {
- // Cancel the outgoing thread.
- if (mHeadsetService != null) {
- mHeadsetService.cancelConnectThread();
- }
- // HeadsetService is down. Phone process most likely crashed.
- // The thread would have got killed.
- }
- }
-
- synchronized void deferProfileServiceMessage(int command) {
- Message msg = new Message();
- msg.what = command;
- deferMessage(msg);
- }
-
- private void updateIncomingAllowedTimer() {
- // Not doing a perfect exponential backoff because
- // we want two different rates. For all practical
- // purposes, this is good enough.
- if (mIncomingRejectTimer == 0) mIncomingRejectTimer = INIT_INCOMING_REJECT_TIMER;
-
- mIncomingRejectTimer *= 5;
- if (mIncomingRejectTimer > MAX_INCOMING_REJECT_TIMER) {
- mIncomingRejectTimer = MAX_INCOMING_REJECT_TIMER;
- }
- writeTimerValue(mIncomingRejectTimer);
- }
-
- private boolean handleIncomingConnection(int command, boolean accept) {
- boolean ret = false;
- Log.i(TAG, "handleIncomingConnection:" + command + ":" + accept);
- switch (command) {
- case CONNECT_HFP_INCOMING:
- if (!accept) {
- ret = mHeadsetService.rejectIncomingConnect(mDevice);
- sendMessage(TRANSITION_TO_STABLE);
- updateIncomingAllowedTimer();
- } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
- writeTimerValue(0);
- ret = mHeadsetService.acceptIncomingConnect(mDevice);
- } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
- writeTimerValue(0);
- handleConnectionOfOtherProfiles(command);
- ret = mHeadsetService.createIncomingConnect(mDevice);
- }
- break;
- case CONNECT_A2DP_INCOMING:
- if (!accept) {
- ret = mA2dpService.allowIncomingConnect(mDevice, false);
- sendMessage(TRANSITION_TO_STABLE);
- updateIncomingAllowedTimer();
- } else {
- writeTimerValue(0);
- ret = mA2dpService.allowIncomingConnect(mDevice, true);
- handleConnectionOfOtherProfiles(command);
- }
- break;
- case CONNECT_HID_INCOMING:
- if (!accept) {
- ret = mService.allowIncomingProfileConnect(mDevice, false);
- sendMessage(TRANSITION_TO_STABLE);
- updateIncomingAllowedTimer();
- } else {
- writeTimerValue(0);
- ret = mService.allowIncomingProfileConnect(mDevice, true);
- }
- break;
- default:
- Log.e(TAG, "Waiting for incoming connection but state changed to:" + command);
- break;
- }
- return ret;
- }
-
- private void sendConnectionAccessIntent() {
- mConnectionAccessReplyReceived = false;
-
- if (!mPowerManager.isScreenOn()) mWakeLock.acquire();
-
- Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST);
- intent.setClassName(ACCESS_AUTHORITY_PACKAGE, ACCESS_AUTHORITY_CLASS);
- intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
- BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- }
-
- private void sendConnectionAccessRemovalIntent() {
- mWakeLock.release();
- Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- }
-
- private int getTrust() {
- String address = mDevice.getAddress();
- if (mIncomingConnections != null) return mIncomingConnections.first;
- return CONNECTION_ACCESS_UNDEFINED;
- }
-
-
- private String getStringValue(long value) {
- StringBuilder sbr = new StringBuilder();
- sbr.append(Long.toString(System.currentTimeMillis()));
- sbr.append("-");
- sbr.append(Long.toString(value));
- return sbr.toString();
- }
-
- private void setTrust(int value) {
- String second;
- if (mIncomingConnections == null) {
- second = getStringValue(INIT_INCOMING_REJECT_TIMER);
- } else {
- second = mIncomingConnections.second;
- }
-
- mIncomingConnections = new Pair(value, second);
- mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections);
- }
-
- private void writeTimerValue(long value) {
- Integer first;
- if (mIncomingConnections == null) {
- first = CONNECTION_ACCESS_UNDEFINED;
- } else {
- first = mIncomingConnections.first;
- }
- mIncomingConnections = new Pair(first, getStringValue(value));
- mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections);
- }
-
- private long readTimerValue() {
- if (mIncomingConnections == null)
- return 0;
- String value = mIncomingConnections.second;
- String[] splits = value.split("-");
- if (splits != null && splits.length == 2) {
- return Long.parseLong(splits[1]);
- }
- return 0;
- }
-
- private boolean readIncomingAllowedValue() {
- if (readTimerValue() == 0) return true;
- String value = mIncomingConnections.second;
- String[] splits = value.split("-");
- if (splits != null && splits.length == 2) {
- long val1 = Long.parseLong(splits[0]);
- long val2 = Long.parseLong(splits[1]);
- if (val1 + val2 <= System.currentTimeMillis()) {
- return true;
- }
- }
- return false;
- }
-
- synchronized boolean processCommand(int command) {
- log("Processing command:" + command);
- switch(command) {
- case CONNECT_HFP_OUTGOING:
- if (mHeadsetService == null) {
- deferProfileServiceMessage(command);
- } else {
- return mHeadsetService.connectHeadsetInternal(mDevice);
- }
- break;
- case CONNECT_HFP_INCOMING:
- if (mHeadsetService == null) {
- deferProfileServiceMessage(command);
- } else {
- processIncomingConnectCommand(command);
- return true;
- }
- break;
- case CONNECT_A2DP_OUTGOING:
- if (mA2dpService != null) {
- return mA2dpService.connectSinkInternal(mDevice);
- }
- break;
- case CONNECT_A2DP_INCOMING:
- processIncomingConnectCommand(command);
- return true;
- case CONNECT_HID_OUTGOING:
- return mService.connectInputDeviceInternal(mDevice);
- case CONNECT_HID_INCOMING:
- processIncomingConnectCommand(command);
- return true;
- case DISCONNECT_HFP_OUTGOING:
- if (mHeadsetService == null) {
- deferProfileServiceMessage(command);
- } else {
- // Disconnect PBAP
- // TODO(): Add PBAP to the state machine.
- Message m = new Message();
- m.what = DISCONNECT_PBAP_OUTGOING;
- deferMessage(m);
- if (mHeadsetService.getPriority(mDevice) ==
- BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
- mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
- }
- return mHeadsetService.disconnectHeadsetInternal(mDevice);
- }
- break;
- case DISCONNECT_HFP_INCOMING:
- // ignore
- return true;
- case DISCONNECT_A2DP_INCOMING:
- // ignore
- return true;
- case DISCONNECT_A2DP_OUTGOING:
- if (mA2dpService != null) {
- if (mA2dpService.getPriority(mDevice) ==
- BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
- mA2dpService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
- }
- return mA2dpService.disconnectSinkInternal(mDevice);
- }
- break;
- case DISCONNECT_HID_INCOMING:
- // ignore
- return true;
- case DISCONNECT_HID_OUTGOING:
- if (mService.getInputDevicePriority(mDevice) ==
- BluetoothInputDevice.PRIORITY_AUTO_CONNECT) {
- mService.setInputDevicePriority(mDevice, BluetoothInputDevice.PRIORITY_ON);
- }
- return mService.disconnectInputDeviceInternal(mDevice);
- case DISCONNECT_PBAP_OUTGOING:
- if (!mPbapServiceConnected) {
- deferProfileServiceMessage(command);
- } else {
- return mPbapService.disconnect();
- }
- break;
- case UNPAIR:
- writeTimerValue(INIT_INCOMING_REJECT_TIMER);
- setTrust(CONNECTION_ACCESS_UNDEFINED);
- return mService.removeBondInternal(mDevice.getAddress());
- default:
- Log.e(TAG, "Error: Unknown Command");
- }
- return false;
- }
-
- private void processIncomingConnectCommand(int command) {
- // Check if device is already trusted
- int access = getTrust();
- if (access == BluetoothDevice.CONNECTION_ACCESS_YES) {
- handleIncomingConnection(command, true);
- } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO &&
- !readIncomingAllowedValue()) {
- handleIncomingConnection(command, false);
- } else {
- sendConnectionAccessIntent();
- Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY);
- sendMessageDelayed(msg,
- CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT);
- }
- }
-
- private void handleConnectionOfOtherProfiles(int command) {
- // The white paper recommendations mentions that when there is a
- // link loss, it is the responsibility of the remote device to connect.
- // Many connect only 1 profile - and they connect the second profile on
- // some user action (like play being pressed) and so we need this code.
- // Auto Connect code only connects to the last connected device - which
- // is useful in cases like when the phone reboots. But consider the
- // following case:
- // User is connected to the car's phone and A2DP profile.
- // User comes to the desk and places the phone in the dock
- // (or any speaker or music system or even another headset) and thus
- // gets connected to the A2DP profile. User goes back to the car.
- // Ideally the car's system is supposed to send incoming connections
- // from both Handsfree and A2DP profile. But they don't. The Auto
- // connect code, will not work here because we only auto connect to the
- // last connected device for that profile which in this case is the dock.
- // Now suppose a user is using 2 headsets simultaneously, one for the
- // phone profile one for the A2DP profile. If this is the use case, we
- // expect the user to use the preference to turn off the A2DP profile in
- // the Settings screen for the first headset. Else, after link loss,
- // there can be an incoming connection from the first headset which
- // might result in the connection of the A2DP profile (if the second
- // headset is slower) and thus the A2DP profile on the second headset
- // will never get connected.
- //
- // TODO(): Handle other profiles here.
- switch (command) {
- case CONNECT_HFP_INCOMING:
- // Connect A2DP if there is no incoming connection
- // If the priority is OFF - don't auto connect.
- if (mA2dpService.getPriority(mDevice) == BluetoothProfile.PRIORITY_ON ||
- mA2dpService.getPriority(mDevice) ==
- BluetoothProfile.PRIORITY_AUTO_CONNECT) {
- Message msg = new Message();
- msg.what = CONNECT_OTHER_PROFILES;
- msg.arg1 = CONNECT_A2DP_OUTGOING;
- sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY);
- }
- break;
- case CONNECT_A2DP_INCOMING:
- // This is again against spec. HFP incoming connections should be made
- // before A2DP, so we should not hit this case. But many devices
- // don't follow this.
- if (mHeadsetService != null &&
- (mHeadsetService.getPriority(mDevice) == BluetoothProfile.PRIORITY_ON ||
- mHeadsetService.getPriority(mDevice) ==
- BluetoothProfile.PRIORITY_AUTO_CONNECT)) {
- Message msg = new Message();
- msg.what = CONNECT_OTHER_PROFILES;
- msg.arg1 = CONNECT_HFP_OUTGOING;
- sendMessageDelayed(msg, CONNECT_OTHER_PROFILES_DELAY);
- }
- break;
- default:
- break;
- }
-
- }
-
- /*package*/ BluetoothDevice getDevice() {
- return mDevice;
- }
-
- /**
- * Quit the state machine, only to be called by BluetoothService.
- *
- * @hide
- */
- public void doQuit() {
- this.quit();
- }
-
- private void log(String message) {
- if (DBG) {
- Log.i(TAG, "Device:" + mDevice + " Message:" + message);
- }
- }
-}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 2bbf008..541b69f 100644..100755
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -45,7 +45,7 @@ import java.util.List;
*/
public final class BluetoothHeadset implements BluetoothProfile {
private static final String TAG = "BluetoothHeadset";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
/**
* Intent used to broadcast the change in connection state of the Headset
@@ -219,7 +219,38 @@ public final class BluetoothHeadset implements BluetoothProfile {
private Context mContext;
private ServiceListener mServiceListener;
private IBluetoothHeadset mService;
- BluetoothAdapter mAdapter;
+ private BluetoothAdapter mAdapter;
+
+ final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (!up) {
+ if (DBG) 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 (DBG) 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");
+ }
+ }
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
+ }
+ }
+ }
+ }
+ };
/**
* Create a BluetoothHeadset proxy object.
@@ -228,6 +259,16 @@ public final class BluetoothHeadset implements BluetoothProfile {
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);
+ }
+ }
+
if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
Log.e(TAG, "Could not bind to Bluetooth Headset Service");
}
@@ -239,11 +280,27 @@ public final class BluetoothHeadset implements BluetoothProfile {
* results once close() has been called. Multiple invocations of close()
* are ok.
*/
- /*package*/ synchronized void close() {
+ /*package*/ void close() {
if (DBG) log("close()");
- if (mConnection != null) {
- mContext.unbindService(mConnection);
- mConnection = null;
+
+ 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;
}
@@ -562,25 +619,6 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
/**
- * Cancel the outgoing connection.
- * Note: This is an internal function and shouldn't be exposed
- *
- * @hide
- */
- public boolean cancelConnectThread() {
- if (DBG) log("cancelConnectThread");
- if (mService != null && isEnabled()) {
- try {
- return mService.cancelConnectThread();
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
- return false;
- }
-
- /**
* Accept the incoming connection.
* Note: This is an internal function and shouldn't be exposed
*
@@ -600,25 +638,6 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
/**
- * Create the connect thread for the incoming connection.
- * Note: This is an internal function and shouldn't be exposed
- *
- * @hide
- */
- public boolean createIncomingConnect(BluetoothDevice device) {
- if (DBG) log("createIncomingConnect");
- if (mService != null && isEnabled()) {
- try {
- return mService.createIncomingConnect(device);
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
- }
- return false;
- }
-
- /**
* Reject the incoming connection.
* @hide
*/
@@ -636,55 +655,63 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
/**
- * Connect to a Bluetooth Headset.
+ * Get the current audio state of the Headset.
* Note: This is an internal function and shouldn't be exposed
*
* @hide
*/
- public boolean connectHeadsetInternal(BluetoothDevice device) {
- if (DBG) log("connectHeadsetInternal");
- if (mService != null && isEnabled()) {
+ public int getAudioState(BluetoothDevice device) {
+ if (DBG) log("getAudioState");
+ if (mService != null && !isDisabled()) {
try {
- return mService.connectHeadsetInternal(device);
+ return mService.getAudioState(device);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
} else {
Log.w(TAG, "Proxy not attached to service");
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
}
- return false;
+ return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
}
/**
- * Disconnect a Bluetooth Headset.
- * Note: This is an internal function and shouldn't be exposed
+ * Check if Bluetooth SCO audio is connected.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
+ * @return true if SCO is connected,
+ * false otherwise or on error
* @hide
*/
- public boolean disconnectHeadsetInternal(BluetoothDevice device) {
- if (DBG) log("disconnectHeadsetInternal");
- if (mService != null && !isDisabled()) {
+ public boolean isAudioOn() {
+ if (DBG) log("isAudioOn()");
+ if (mService != null && isEnabled()) {
try {
- return mService.disconnectHeadsetInternal(device);
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
- } else {
- Log.w(TAG, "Proxy not attached to service");
- if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ return mService.isAudioOn();
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
}
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
return false;
+
}
/**
- * Set the audio state of the Headset.
- * Note: This is an internal function and shouldn't be exposed
+ * Initiates a connection of headset audio.
+ * It setup SCO channel with remote connected headset device.
*
+ * @return true if successful
+ * false if there was some error such as
+ * there is no connected headset
* @hide
*/
- public boolean setAudioState(BluetoothDevice device, int state) {
- if (DBG) log("setAudioState");
- if (mService != null && !isDisabled()) {
+ public boolean connectAudio() {
+ if (mService != null && isEnabled()) {
try {
- return mService.setAudioState(device, state);
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ return mService.connectAudio();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
} else {
Log.w(TAG, "Proxy not attached to service");
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
@@ -693,22 +720,26 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
/**
- * Get the current audio state of the Headset.
- * Note: This is an internal function and shouldn't be exposed
+ * Initiates a disconnection of headset audio.
+ * It tears down the SCO channel from remote headset device.
*
+ * @return true if successful
+ * false if there was some error such as
+ * there is no connected SCO channel
* @hide
*/
- public int getAudioState(BluetoothDevice device) {
- if (DBG) log("getAudioState");
- if (mService != null && !isDisabled()) {
+ public boolean disconnectAudio() {
+ if (mService != null && isEnabled()) {
try {
- return mService.getAudioState(device);
- } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ return mService.disconnectAudio();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
} else {
Log.w(TAG, "Proxy not attached to service");
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
}
- return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+ return false;
}
/**
@@ -760,6 +791,68 @@ public final class BluetoothHeadset implements BluetoothProfile {
return false;
}
+ /**
+ * Notify Headset of phone state change.
+ * This is a backdoor for phone app to call BluetoothHeadset since
+ * there is currently not a good way to get precise call state change outside
+ * of phone app.
+ *
+ * @hide
+ */
+ public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
+ int type) {
+ if (mService != null && isEnabled()) {
+ try {
+ mService.phoneStateChanged(numActive, numHeld, callState, number, type);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @hide
+ */
+ public void roamChanged(boolean roaming) {
+ if (mService != null && isEnabled()) {
+ try {
+ mService.roamChanged(roaming);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+
+ /**
+ * Send Headset of CLCC response
+ *
+ * @hide
+ */
+ public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
+ String number, int type) {
+ if (mService != null && isEnabled()) {
+ try {
+ mService.clccResponse(index, direction, status, mode, mpty, number, type);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "Proxy object connected");
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
index f850c02..4a0bc7e 100644
--- a/core/java/android/bluetooth/BluetoothHealth.java
+++ b/core/java/android/bluetooth/BluetoothHealth.java
@@ -16,7 +16,10 @@
package android.bluetooth;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -54,7 +57,7 @@ import java.util.List;
*/
public final class BluetoothHealth implements BluetoothProfile {
private static final String TAG = "BluetoothHealth";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
/**
* Health Profile Source Role - the health device.
@@ -94,6 +97,37 @@ public final class BluetoothHealth implements BluetoothProfile {
/** @hide */
public static final int HEALTH_OPERATION_NOT_ALLOWED = 6005;
+ final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (!up) {
+ if (DBG) 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 (DBG) 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");
+ }
+ }
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
+ }
+ }
+ }
+ }
+ };
+
/**
* Register an application configuration that acts as a Health SINK.
@@ -427,35 +461,74 @@ public final class BluetoothHealth implements BluetoothProfile {
/** Health App Configuration un-registration failure */
public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
+ private Context mContext;
private ServiceListener mServiceListener;
- private IBluetooth mService;
+ private IBluetoothHealth mService;
BluetoothAdapter mAdapter;
/**
* Create a BluetoothHealth proxy object.
*/
- /*package*/ BluetoothHealth(Context mContext, ServiceListener l) {
- IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
+ /*package*/ BluetoothHealth(Context context, ServiceListener l) {
+ mContext = context;
mServiceListener = l;
mAdapter = BluetoothAdapter.getDefaultAdapter();
- if (b != null) {
- mService = IBluetooth.Stub.asInterface(b);
- if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, this);
+ IBluetoothManager mgr = mAdapter.getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
}
- } else {
- Log.w(TAG, "Bluetooth Service not available!");
+ }
- // Instead of throwing an exception which prevents people from going
- // into Wireless settings in the emulator. Let it crash later when it is actually used.
- mService = null;
+ if (!context.bindService(new Intent(IBluetoothHealth.class.getName()), mConnection, 0)) {
+ Log.e(TAG, "Could not bind to Bluetooth Health Service");
}
}
/*package*/ void close() {
+ if (DBG) log("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;
}
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if (DBG) Log.d(TAG, "Proxy object connected");
+ mService = IBluetoothHealth.Stub.asInterface(service);
+
+ if (mServiceListener != null) {
+ mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, BluetoothHealth.this);
+ }
+ }
+ public void onServiceDisconnected(ComponentName className) {
+ if (DBG) Log.d(TAG, "Proxy object disconnected");
+ mService = null;
+ if (mServiceListener != null) {
+ mServiceListener.onServiceDisconnected(BluetoothProfile.HEALTH);
+ }
+ }
+ };
+
private boolean isEnabled() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java
index 1a9e011..bff966d 100644..100755
--- a/core/java/android/bluetooth/BluetoothInputDevice.java
+++ b/core/java/android/bluetooth/BluetoothInputDevice.java
@@ -18,7 +18,10 @@ package android.bluetooth;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -41,7 +44,7 @@ import java.util.List;
*/
public final class BluetoothInputDevice implements BluetoothProfile {
private static final String TAG = "BluetoothInputDevice";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
/**
* Intent used to broadcast the change in connection state of the Input
@@ -66,6 +69,22 @@ public final class BluetoothInputDevice implements BluetoothProfile {
"android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
/**
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PROTOCOL_MODE_CHANGED =
+ "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED";
+
+
+ /**
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_VIRTUAL_UNPLUG_STATUS =
+ "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS";
+
+
+ /**
* Return codes for the connect and disconnect Bluez / Dbus calls.
* @hide
*/
@@ -91,34 +110,159 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public static final int INPUT_OPERATION_SUCCESS = 5004;
+ /**
+ * @hide
+ */
+ public static final int PROTOCOL_REPORT_MODE = 0;
+
+ /**
+ * @hide
+ */
+ public static final int PROTOCOL_BOOT_MODE = 1;
+
+ /**
+ * @hide
+ */
+ public static final int PROTOCOL_UNSUPPORTED_MODE = 255;
+
+ /* int reportType, int reportType, int bufferSize */
+ /**
+ * @hide
+ */
+ public static final byte REPORT_TYPE_INPUT = 0;
+
+ /**
+ * @hide
+ */
+ public static final byte REPORT_TYPE_OUTPUT = 1;
+
+ /**
+ * @hide
+ */
+ public static final byte REPORT_TYPE_FEATURE = 2;
+
+ /**
+ * @hide
+ */
+ public static final int VIRTUAL_UNPLUG_STATUS_SUCCESS = 0;
+
+ /**
+ * @hide
+ */
+ public static final int VIRTUAL_UNPLUG_STATUS_FAIL = 1;
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_PROTOCOL_MODE = "android.bluetooth.BluetoothInputDevice.extra.PROTOCOL_MODE";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_REPORT_TYPE = "android.bluetooth.BluetoothInputDevice.extra.REPORT_TYPE";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_REPORT_ID = "android.bluetooth.BluetoothInputDevice.extra.REPORT_ID";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_REPORT_BUFFER_SIZE = "android.bluetooth.BluetoothInputDevice.extra.REPORT_BUFFER_SIZE";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_REPORT = "android.bluetooth.BluetoothInputDevice.extra.REPORT";
+
+ /**
+ * @hide
+ */
+ public static final String EXTRA_VIRTUAL_UNPLUG_STATUS = "android.bluetooth.BluetoothInputDevice.extra.VIRTUAL_UNPLUG_STATUS";
+
+ private Context mContext;
private ServiceListener mServiceListener;
private BluetoothAdapter mAdapter;
- private IBluetooth mService;
+ private IBluetoothInputDevice mService;
+
+ final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (!up) {
+ if (DBG) 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 (DBG) 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");
+ }
+ }
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
+ }
+ }
+ }
+ }
+ };
/**
* Create a BluetoothInputDevice proxy object for interacting with the local
* Bluetooth Service which handles the InputDevice profile
*
*/
- /*package*/ BluetoothInputDevice(Context mContext, ServiceListener l) {
- IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
+ /*package*/ BluetoothInputDevice(Context context, ServiceListener l) {
+ mContext = context;
mServiceListener = l;
mAdapter = BluetoothAdapter.getDefaultAdapter();
- if (b != null) {
- mService = IBluetooth.Stub.asInterface(b);
- if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE, this);
+
+ IBluetoothManager mgr = mAdapter.getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
}
- } else {
- Log.w(TAG, "Bluetooth Service not available!");
+ }
- // Instead of throwing an exception which prevents people from going
- // into Wireless settings in the emulator. Let it crash later when it is actually used.
- mService = null;
+ if (!context.bindService(new Intent(IBluetoothInputDevice.class.getName()),
+ mConnection, 0)) {
+ Log.e(TAG, "Could not bind to Bluetooth HID Service");
}
}
/*package*/ void close() {
+ if (DBG) log("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;
}
@@ -144,10 +288,9 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
- if (mService != null && isEnabled() &&
- isValidDevice(device)) {
+ if (mService != null && isEnabled() && isValidDevice(device)) {
try {
- return mService.connectInputDevice(device);
+ return mService.connect(device);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return false;
@@ -185,10 +328,9 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
- if (mService != null && isEnabled() &&
- isValidDevice(device)) {
+ if (mService != null && isEnabled() && isValidDevice(device)) {
try {
- return mService.disconnectInputDevice(device);
+ return mService.disconnect(device);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return false;
@@ -205,7 +347,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
if (DBG) log("getConnectedDevices()");
if (mService != null && isEnabled()) {
try {
- return mService.getConnectedInputDevices();
+ return mService.getConnectedDevices();
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return new ArrayList<BluetoothDevice>();
@@ -222,7 +364,7 @@ public final class BluetoothInputDevice implements BluetoothProfile {
if (DBG) log("getDevicesMatchingStates()");
if (mService != null && isEnabled()) {
try {
- return mService.getInputDevicesMatchingConnectionStates(states);
+ return mService.getDevicesMatchingConnectionStates(states);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return new ArrayList<BluetoothDevice>();
@@ -237,10 +379,9 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public int getConnectionState(BluetoothDevice device) {
if (DBG) log("getState(" + device + ")");
- if (mService != null && isEnabled()
- && isValidDevice(device)) {
+ if (mService != null && isEnabled() && isValidDevice(device)) {
try {
- return mService.getInputDeviceConnectionState(device);
+ return mService.getConnectionState(device);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return BluetoothProfile.STATE_DISCONNECTED;
@@ -267,14 +408,13 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
- if (mService != null && isEnabled()
- && isValidDevice(device)) {
+ if (mService != null && isEnabled() && isValidDevice(device)) {
if (priority != BluetoothProfile.PRIORITY_OFF &&
priority != BluetoothProfile.PRIORITY_ON) {
return false;
}
try {
- return mService.setInputDevicePriority(device, priority);
+ return mService.setPriority(device, priority);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return false;
@@ -299,10 +439,9 @@ public final class BluetoothInputDevice implements BluetoothProfile {
*/
public int getPriority(BluetoothDevice device) {
if (DBG) log("getPriority(" + device + ")");
- if (mService != null && isEnabled()
- && isValidDevice(device)) {
+ if (mService != null && isEnabled() && isValidDevice(device)) {
try {
- return mService.getInputDevicePriority(device);
+ return mService.getPriority(device);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return BluetoothProfile.PRIORITY_OFF;
@@ -312,6 +451,24 @@ public final class BluetoothInputDevice implements BluetoothProfile {
return BluetoothProfile.PRIORITY_OFF;
}
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if (DBG) Log.d(TAG, "Proxy object connected");
+ mService = IBluetoothInputDevice.Stub.asInterface(service);
+
+ if (mServiceListener != null) {
+ mServiceListener.onServiceConnected(BluetoothProfile.INPUT_DEVICE, BluetoothInputDevice.this);
+ }
+ }
+ public void onServiceDisconnected(ComponentName className) {
+ if (DBG) Log.d(TAG, "Proxy object disconnected");
+ mService = null;
+ if (mServiceListener != null) {
+ mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_DEVICE);
+ }
+ }
+ };
+
private boolean isEnabled() {
if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
return false;
@@ -324,6 +481,158 @@ public final class BluetoothInputDevice implements BluetoothProfile {
return false;
}
+
+ /**
+ * Initiate virtual unplug for a HID input device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param device Remote Bluetooth Device
+ * @return false on immediate error,
+ * true otherwise
+ * @hide
+ */
+ public boolean virtualUnplug(BluetoothDevice device) {
+ if (DBG) log("virtualUnplug(" + device + ")");
+ if (mService != null && isEnabled() && isValidDevice(device)) {
+ try {
+ return mService.virtualUnplug(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+
+ }
+
+ /**
+ * Send Get_Protocol_Mode command to the connected HID input device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param device Remote Bluetooth Device
+ * @return false on immediate error,
+ *true otherwise
+ * @hide
+ */
+ public boolean getProtocolMode(BluetoothDevice device) {
+ if (DBG) log("getProtocolMode(" + device + ")");
+ if (mService != null && isEnabled() && isValidDevice(device)) {
+ try {
+ return mService.getProtocolMode(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * Send Set_Protocol_Mode command to the connected HID input device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param device Remote Bluetooth Device
+ * @return false on immediate error,
+ * true otherwise
+ * @hide
+ */
+ public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
+ if (DBG) log("setProtocolMode(" + device + ")");
+ if (mService != null && isEnabled() && isValidDevice(device)) {
+ try {
+ return mService.setProtocolMode(device, protocolMode);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * Send Get_Report command to the connected HID input device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param device Remote Bluetooth Device
+ * @param reportType Report type
+ * @param reportId Report ID
+ * @param bufferSize Report receiving buffer size
+ * @return false on immediate error,
+ * true otherwise
+ * @hide
+ */
+ public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) {
+ if (DBG) log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId + "bufferSize=" + bufferSize);
+ if (mService != null && isEnabled() && isValidDevice(device)) {
+ try {
+ return mService.getReport(device, reportType, reportId, bufferSize);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * Send Set_Report command to the connected HID input device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param device Remote Bluetooth Device
+ * @param reportType Report type
+ * @param report Report receiving buffer size
+ * @return false on immediate error,
+ * true otherwise
+ * @hide
+ */
+ public boolean setReport(BluetoothDevice device, byte reportType, String report) {
+ if (DBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
+ if (mService != null && isEnabled() && isValidDevice(device)) {
+ try {
+ return mService.setReport(device, reportType, report);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * Send Send_Data command to the connected HID input device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param device Remote Bluetooth Device
+ * @param data Data to send
+ * @return false on immediate error,
+ * true otherwise
+ * @hide
+ */
+ public boolean sendData(BluetoothDevice device, String report) {
+ if (DBG) log("sendData(" + device + "), report=" + report);
+ if (mService != null && isEnabled() && isValidDevice(device)) {
+ try {
+ return mService.sendData(device, report);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
private static void log(String msg) {
Log.d(TAG, msg);
}
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 5d9d8be..cae7a73 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -18,7 +18,10 @@ package android.bluetooth;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -27,7 +30,6 @@ import android.util.Log;
import java.util.ArrayList;
import java.util.List;
-
/**
* This class provides the APIs to control the Bluetooth Pan
* Profile.
@@ -41,7 +43,7 @@ import java.util.List;
*/
public final class BluetoothPan implements BluetoothProfile {
private static final String TAG = "BluetoothPan";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
/**
* Intent used to broadcast the change in connection state of the Pan
@@ -76,15 +78,18 @@ public final class BluetoothPan implements BluetoothProfile {
*/
public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
+ public static final int PAN_ROLE_NONE = 0;
/**
* The local device is acting as a Network Access Point.
*/
public static final int LOCAL_NAP_ROLE = 1;
+ public static final int REMOTE_NAP_ROLE = 1;
/**
* The local device is acting as a PAN User.
*/
public static final int LOCAL_PANU_ROLE = 2;
+ public static final int REMOTE_PANU_ROLE = 2;
/**
* Return codes for the connect and disconnect Bluez / Dbus calls.
@@ -112,37 +117,77 @@ public final class BluetoothPan implements BluetoothProfile {
*/
public static final int PAN_OPERATION_SUCCESS = 1004;
+ private Context mContext;
private ServiceListener mServiceListener;
private BluetoothAdapter mAdapter;
- private IBluetooth mService;
+ private IBluetoothPan mPanService;
/**
* Create a BluetoothPan proxy object for interacting with the local
* Bluetooth Service which handles the Pan profile
*
*/
- /*package*/ BluetoothPan(Context mContext, ServiceListener l) {
- IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
+ /*package*/ BluetoothPan(Context context, ServiceListener l) {
+ mContext = context;
mServiceListener = l;
mAdapter = BluetoothAdapter.getDefaultAdapter();
- if (b != null) {
- mService = IBluetooth.Stub.asInterface(b);
- if (mServiceListener != null) {
- mServiceListener.onServiceConnected(BluetoothProfile.PAN, this);
- }
- } else {
- Log.w(TAG, "Bluetooth Service not available!");
-
- // Instead of throwing an exception which prevents people from going
- // into Wireless settings in the emulator. Let it crash later when it is actually used.
- mService = null;
+ try {
+ mAdapter.getBluetoothManager().registerStateChangeCallback(mStateChangeCallback);
+ } 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");
}
+ Log.d(TAG, "BluetoothPan(), bindService called");
}
/*package*/ void close() {
+ if (DBG) log("close()");
+ if (mConnection != null) {
+ mContext.unbindService(mConnection);
+ mConnection = null;
+ }
mServiceListener = null;
+ try {
+ mAdapter.getBluetoothManager().unregisterStateChangeCallback(mStateChangeCallback);
+ } catch (RemoteException re) {
+ Log.w(TAG,"Unable to register BluetoothStateChangeCallback",re);
+ }
}
+ protected void finalize() {
+ close();
+ }
+
+ 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");
+ } else {
+ if (DBG) Log.d(TAG,"Unbinding service...");
+ synchronized (mConnection) {
+ try {
+ mPanService = null;
+ mContext.unbindService(mConnection);
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
+ }
+ }
+ }
+ }
+ };
+
/**
* Initiate connection to a profile of the remote bluetooth device.
*
@@ -163,16 +208,16 @@ public final class BluetoothPan implements BluetoothProfile {
*/
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
- if (mService != null && isEnabled() &&
+ if (mPanService != null && isEnabled() &&
isValidDevice(device)) {
try {
- return mService.connectPanDevice(device);
+ return mPanService.connect(device);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return false;
}
}
- if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ if (mPanService == null) Log.w(TAG, "Proxy not attached to service");
return false;
}
@@ -204,16 +249,16 @@ public final class BluetoothPan implements BluetoothProfile {
*/
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
- if (mService != null && isEnabled() &&
+ if (mPanService != null && isEnabled() &&
isValidDevice(device)) {
try {
- return mService.disconnectPanDevice(device);
+ return mPanService.disconnect(device);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return false;
}
}
- if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ if (mPanService == null) Log.w(TAG, "Proxy not attached to service");
return false;
}
@@ -222,15 +267,15 @@ public final class BluetoothPan implements BluetoothProfile {
*/
public List<BluetoothDevice> getConnectedDevices() {
if (DBG) log("getConnectedDevices()");
- if (mService != null && isEnabled()) {
+ if (mPanService != null && isEnabled()) {
try {
- return mService.getConnectedPanDevices();
+ return mPanService.getConnectedDevices();
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return new ArrayList<BluetoothDevice>();
}
}
- if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ if (mPanService == null) Log.w(TAG, "Proxy not attached to service");
return new ArrayList<BluetoothDevice>();
}
@@ -239,15 +284,15 @@ public final class BluetoothPan implements BluetoothProfile {
*/
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) log("getDevicesMatchingStates()");
- if (mService != null && isEnabled()) {
+ if (mPanService != null && isEnabled()) {
try {
- return mService.getPanDevicesMatchingConnectionStates(states);
+ return mPanService.getDevicesMatchingConnectionStates(states);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return new ArrayList<BluetoothDevice>();
}
}
- if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ if (mPanService == null) Log.w(TAG, "Proxy not attached to service");
return new ArrayList<BluetoothDevice>();
}
@@ -256,23 +301,23 @@ public final class BluetoothPan implements BluetoothProfile {
*/
public int getConnectionState(BluetoothDevice device) {
if (DBG) log("getState(" + device + ")");
- if (mService != null && isEnabled()
+ if (mPanService != null && isEnabled()
&& isValidDevice(device)) {
try {
- return mService.getPanDeviceConnectionState(device);
+ return mPanService.getConnectionState(device);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
return BluetoothProfile.STATE_DISCONNECTED;
}
}
- if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ if (mPanService == null) Log.w(TAG, "Proxy not attached to service");
return BluetoothProfile.STATE_DISCONNECTED;
}
public void setBluetoothTethering(boolean value) {
if (DBG) log("setBluetoothTethering(" + value + ")");
try {
- mService.setBluetoothTethering(value);
+ mPanService.setBluetoothTethering(value);
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
}
@@ -281,13 +326,32 @@ public final class BluetoothPan implements BluetoothProfile {
public boolean isTetheringOn() {
if (DBG) log("isTetheringOn()");
try {
- return mService.isTetheringOn();
+ return mPanService.isTetheringOn();
} catch (RemoteException e) {
Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
- return false;
}
+ return false;
}
+ private 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);
+
+ if (mServiceListener != null) {
+ mServiceListener.onServiceConnected(BluetoothProfile.PAN,
+ BluetoothPan.this);
+ }
+ }
+ public void onServiceDisconnected(ComponentName className) {
+ if (DBG) Log.d(TAG, "BluetoothPAN Proxy object disconnected");
+ mPanService = null;
+ if (mServiceListener != null) {
+ mServiceListener.onServiceDisconnected(BluetoothProfile.PAN);
+ }
+ }
+ };
+
private boolean isEnabled() {
if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
return false;
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 639ae1a..7de2ef6 100644..100755
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -70,6 +70,7 @@ public class BluetoothPbap {
private IBluetoothPbap 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;
@@ -96,7 +97,7 @@ public class BluetoothPbap {
* this callback before making IPC calls on the BluetoothPbap
* service.
*/
- public void onServiceConnected();
+ public void onServiceConnected(BluetoothPbap proxy);
/**
* Called to notify the client that this proxy object has been
@@ -108,12 +109,54 @@ public class BluetoothPbap {
public void onServiceDisconnected();
}
+ final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (!up) {
+ if (DBG) 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 (DBG) 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");
+ }
+ }
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
+ }
+ }
+ }
+ }
+ };
+
/**
* Create a BluetoothPbap proxy object.
*/
public BluetoothPbap(Context context, ServiceListener l) {
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);
+ }
+ }
if (!context.bindService(new Intent(IBluetoothPbap.class.getName()), mConnection, 0)) {
Log.e(TAG, "Could not bind to Bluetooth Pbap Service");
}
@@ -134,9 +177,25 @@ public class BluetoothPbap {
* are ok.
*/
public synchronized void close() {
- if (mConnection != null) {
- mContext.unbindService(mConnection);
- mConnection = null;
+ 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);
+ mConnection = null;
+ } catch (Exception re) {
+ Log.e(TAG,"",re);
+ }
+ }
}
mServiceListener = null;
}
@@ -240,7 +299,7 @@ public class BluetoothPbap {
if (DBG) log("Proxy object connected");
mService = IBluetoothPbap.Stub.asInterface(service);
if (mServiceListener != null) {
- mServiceListener.onServiceConnected();
+ mServiceListener.onServiceConnected(BluetoothPbap.this);
}
}
public void onServiceDisconnected(ComponentName className) {
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 1920efa..1920efa 100644..100755
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java
deleted file mode 100644
index b0c0a0b..0000000
--- a/core/java/android/bluetooth/BluetoothProfileState.java
+++ /dev/null
@@ -1,160 +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 android.bluetooth;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Message;
-import android.util.Log;
-
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-
-/**
- * This state machine is used to serialize the connections
- * to a particular profile. Currently, we only allow one device
- * to be connected to a particular profile.
- * States:
- * {@link StableState} : No pending commands. Send the
- * command to the appropriate remote device specific state machine.
- *
- * {@link PendingCommandState} : A profile connection / disconnection
- * command is being executed. This will result in a profile state
- * change. Defer all commands.
- * @hide
- */
-
-public class BluetoothProfileState extends StateMachine {
- private static final boolean DBG = true;
- private static final String TAG = "BluetoothProfileState";
-
- public static final int HFP = 0;
- public static final int A2DP = 1;
- public static final int HID = 2;
-
- static final int TRANSITION_TO_STABLE = 100;
-
- private int mProfile;
- private BluetoothDevice mPendingDevice;
- private PendingCommandState mPendingCommandState = new PendingCommandState();
- private StableState mStableState = new StableState();
-
- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (device == null) {
- return;
- }
- if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
- int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
- if (mProfile == HFP && (newState == BluetoothProfile.STATE_CONNECTED ||
- newState == BluetoothProfile.STATE_DISCONNECTED)) {
- sendMessage(TRANSITION_TO_STABLE);
- }
- } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
- int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
- if (mProfile == A2DP && (newState == BluetoothProfile.STATE_CONNECTED ||
- newState == BluetoothProfile.STATE_DISCONNECTED)) {
- sendMessage(TRANSITION_TO_STABLE);
- }
- } else if (action.equals(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED)) {
- int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
- if (mProfile == HID && (newState == BluetoothProfile.STATE_CONNECTED ||
- newState == BluetoothProfile.STATE_DISCONNECTED)) {
- sendMessage(TRANSITION_TO_STABLE);
- }
- } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
- if (device.equals(mPendingDevice)) {
- sendMessage(TRANSITION_TO_STABLE);
- }
- }
- }
- };
-
- public BluetoothProfileState(Context context, int profile) {
- super("BluetoothProfileState:" + profile);
- mProfile = profile;
- addState(mStableState);
- addState(mPendingCommandState);
- setInitialState(mStableState);
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
- context.registerReceiver(mBroadcastReceiver, filter);
- }
-
- private class StableState extends State {
- @Override
- public void enter() {
- log("Entering Stable State");
- mPendingDevice = null;
- }
-
- @Override
- public boolean processMessage(Message msg) {
- if (msg.what != TRANSITION_TO_STABLE) {
- transitionTo(mPendingCommandState);
- }
- return true;
- }
- }
-
- private class PendingCommandState extends State {
- @Override
- public void enter() {
- log("Entering PendingCommandState State");
- dispatchMessage(getCurrentMessage());
- }
-
- @Override
- public boolean processMessage(Message msg) {
- if (msg.what == TRANSITION_TO_STABLE) {
- transitionTo(mStableState);
- } else {
- dispatchMessage(msg);
- }
- return true;
- }
-
- private void dispatchMessage(Message msg) {
- BluetoothDeviceProfileState deviceProfileMgr =
- (BluetoothDeviceProfileState)msg.obj;
- int cmd = msg.arg1;
- if (mPendingDevice == null || mPendingDevice.equals(deviceProfileMgr.getDevice())) {
- mPendingDevice = deviceProfileMgr.getDevice();
- deviceProfileMgr.sendMessage(cmd);
- } else {
- Message deferMsg = new Message();
- deferMsg.arg1 = cmd;
- deferMsg.obj = deviceProfileMgr;
- deferMessage(deferMsg);
- }
- }
- }
-
- private void log(String message) {
- if (DBG) {
- Log.i(TAG, "Message:" + message);
- }
- }
-}
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index 4021f7b..96be8a2 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -17,6 +17,8 @@
package android.bluetooth;
import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelUuid;
import java.io.Closeable;
import java.io.IOException;
@@ -86,6 +88,22 @@ public final class BluetoothServerSocket implements Closeable {
}
/**
+ * Construct a socket for incoming connections.
+ * @param type type of socket
+ * @param auth require the remote device to be authenticated
+ * @param encrypt require the connection to be encrypted
+ * @param uuid uuid
+ * @throws IOException On error, for example Bluetooth not available, or
+ * insufficient privileges
+ */
+ /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid)
+ throws IOException {
+ mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid);
+ mChannel = mSocket.getPort();
+ }
+
+
+ /**
* Block until a connection is established.
* <p>Returns a connected {@link BluetoothSocket} on successful connection.
* <p>Once this call returns, it can be called again to accept subsequent
@@ -133,7 +151,9 @@ public final class BluetoothServerSocket implements Closeable {
mHandler = handler;
mMessage = message;
}
-
+ /*package*/ void setServiceName(String ServiceName) {
+ mSocket.setServiceName(ServiceName);
+ }
/**
* Returns the channel on which this socket is bound.
* @hide
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 19d13ef..1bc640f 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -1,32 +1,27 @@
/*
- * 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.
+ * Copyright (C) 2012 Google Inc.
*/
package android.bluetooth;
-import android.bluetooth.IBluetoothCallback;
+import android.os.IBinder;
import android.os.ParcelUuid;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
+import java.util.List;
+import android.net.LocalSocket;
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
/**
* A connected or connecting Bluetooth socket.
*
@@ -89,31 +84,40 @@ public final class BluetoothSocket implements Closeable {
/*package*/ static final int EBADFD = 77;
/*package*/ static final int EADDRINUSE = 98;
+ /*package*/ static final int SEC_FLAG_ENCRYPT = 1;
+ /*package*/ static final int SEC_FLAG_AUTH = 1 << 1;
+
private final int mType; /* one of TYPE_RFCOMM etc */
- private final BluetoothDevice mDevice; /* remote device */
- private final String mAddress; /* remote address */
+ private BluetoothDevice mDevice; /* remote device */
+ private String mAddress; /* remote address */
private final boolean mAuth;
private final boolean mEncrypt;
private final BluetoothInputStream mInputStream;
private final BluetoothOutputStream mOutputStream;
- private final SdpHelper mSdp;
-
+ private final ParcelUuid mUuid;
+ private ParcelFileDescriptor mPfd;
+ private LocalSocket mSocket;
+ private InputStream mSocketIS;
+ private OutputStream mSocketOS;
private int mPort; /* RFCOMM channel or L2CAP psm */
+ private int mFd;
+ private String mServiceName;
+ private static int PROXY_CONNECTION_TIMEOUT = 5000;
+
+ private static int SOCK_SIGNAL_SIZE = 16;
private enum SocketState {
INIT,
CONNECTED,
- CLOSED
+ LISTENING,
+ CLOSED,
}
/** prevents all native calls after destroyNative() */
- private SocketState mSocketState;
+ private volatile SocketState mSocketState;
/** protects mSocketState */
- private final ReentrantReadWriteLock mLock;
-
- /** used by native code only */
- private int mSocketData;
+ //private final ReentrantReadWriteLock mLock;
/**
* Construct a BluetoothSocket.
@@ -134,33 +138,52 @@ public final class BluetoothSocket implements Closeable {
throw new IOException("Invalid RFCOMM channel: " + port);
}
}
- if (uuid == null) {
- mPort = port;
- mSdp = null;
- } else {
- mSdp = new SdpHelper(device, uuid);
- mPort = -1;
- }
+ mUuid = uuid;
mType = type;
mAuth = auth;
mEncrypt = encrypt;
mDevice = device;
+ mPort = port;
+ mFd = fd;
+
+ mSocketState = SocketState.INIT;
+
if (device == null) {
- mAddress = null;
+ // Server socket
+ mAddress = BluetoothAdapter.getDefaultAdapter().getAddress();
} else {
+ // Remote socket
mAddress = device.getAddress();
}
- if (fd == -1) {
- initSocketNative();
- } else {
- initSocketFromFdNative(fd);
- }
mInputStream = new BluetoothInputStream(this);
mOutputStream = new BluetoothOutputStream(this);
- mSocketState = SocketState.INIT;
- mLock = new ReentrantReadWriteLock();
}
-
+ private BluetoothSocket(BluetoothSocket s) {
+ mUuid = s.mUuid;
+ mType = s.mType;
+ mAuth = s.mAuth;
+ mEncrypt = s.mEncrypt;
+ mPort = s.mPort;
+ mInputStream = new BluetoothInputStream(this);
+ mOutputStream = new BluetoothOutputStream(this);
+ mServiceName = s.mServiceName;
+ }
+ private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException {
+ BluetoothSocket as = new BluetoothSocket(this);
+ as.mSocketState = SocketState.CONNECTED;
+ FileDescriptor[] fds = mSocket.getAncillaryFileDescriptors();
+ Log.d(TAG, "socket fd passed by stack fds: " + fds);
+ if(fds == null || fds.length != 1) {
+ Log.e(TAG, "socket fd passed from stack failed, fds: " + fds);
+ throw new IOException("bt socket acept failed");
+ }
+ as.mSocket = new LocalSocket(fds[0]);
+ as.mSocketIS = as.mSocket.getInputStream();
+ as.mSocketOS = as.mSocket.getOutputStream();
+ as.mAddress = RemoteAddr;
+ as.mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(RemoteAddr);
+ return as;
+ }
/**
* Construct a BluetoothSocket from address. Used by native code.
* @param type type of socket
@@ -186,67 +209,13 @@ public final class BluetoothSocket implements Closeable {
super.finalize();
}
}
-
- /**
- * Attempt to connect to a remote device.
- * <p>This method will block until a connection is made or the connection
- * fails. If this method returns without an exception then this socket
- * is now connected.
- * <p>Creating new connections to
- * remote Bluetooth devices should not be attempted while device discovery
- * is in progress. Device discovery is a heavyweight procedure on the
- * Bluetooth adapter and will significantly slow a device connection.
- * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing
- * discovery. Discovery is not managed by the Activity,
- * but is run as a system service, so an application should always call
- * {@link BluetoothAdapter#cancelDiscovery()} even if it
- * did not directly request a discovery, just to be sure.
- * <p>{@link #close} can be used to abort this call from another thread.
- * @throws IOException on error, for example connection failure
- */
- public void connect() throws IOException {
- mLock.readLock().lock();
- try {
- if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
-
- if (mSdp != null) {
- mPort = mSdp.doSdp(); // blocks
- }
-
- connectNative(); // blocks
- mSocketState = SocketState.CONNECTED;
- } finally {
- mLock.readLock().unlock();
- }
- }
-
- /**
- * Immediately close this socket, and release all associated resources.
- * <p>Causes blocked calls on this socket in other threads to immediately
- * throw an IOException.
- */
- public void close() throws IOException {
- // abort blocking operations on the socket
- mLock.readLock().lock();
- try {
- if (mSocketState == SocketState.CLOSED) return;
- if (mSdp != null) {
- mSdp.cancel();
- }
- abortNative();
- } finally {
- mLock.readLock().unlock();
- }
-
- // all native calls are guaranteed to immediately return after
- // abortNative(), so this lock should immediately acquire
- mLock.writeLock().lock();
- try {
- mSocketState = SocketState.CLOSED;
- destroyNative();
- } finally {
- mLock.writeLock().unlock();
- }
+ private int getSecurityFlags() {
+ int flags = 0;
+ if(mAuth)
+ flags |= SEC_FLAG_AUTH;
+ if(mEncrypt)
+ flags |= SEC_FLAG_ENCRYPT;
+ return flags;
}
/**
@@ -286,137 +255,235 @@ public final class BluetoothSocket implements Closeable {
* false if not connected
*/
public boolean isConnected() {
- return (mSocketState == SocketState.CONNECTED);
+ return mSocketState == SocketState.CONNECTED;
+ }
+
+ /*package*/ void setServiceName(String name) {
+ mServiceName = name;
}
/**
- * Currently returns unix errno instead of throwing IOException,
- * so that BluetoothAdapter can check the error code for EADDRINUSE
+ * Attempt to connect to a remote device.
+ * <p>This method will block until a connection is made or the connection
+ * fails. If this method returns without an exception then this socket
+ * is now connected.
+ * <p>Creating new connections to
+ * remote Bluetooth devices should not be attempted while device discovery
+ * is in progress. Device discovery is a heavyweight procedure on the
+ * Bluetooth adapter and will significantly slow a device connection.
+ * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing
+ * discovery. Discovery is not managed by the Activity,
+ * but is run as a system service, so an application should always call
+ * {@link BluetoothAdapter#cancelDiscovery()} even if it
+ * did not directly request a discovery, just to be sure.
+ * <p>{@link #close} can be used to abort this call from another thread.
+ * @throws IOException on error, for example connection failure
*/
- /*package*/ int bindListen() {
- mLock.readLock().lock();
- try {
- if (mSocketState == SocketState.CLOSED) return EBADFD;
- return bindListenNative();
- } finally {
- mLock.readLock().unlock();
- }
- }
+ public void connect() throws IOException {
+ if (mDevice == null) throw new IOException("Connect is called on null device");
- /*package*/ BluetoothSocket accept(int timeout) throws IOException {
- mLock.readLock().lock();
try {
+ // TODO(BT) derive flag from auth and encrypt
if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
-
- BluetoothSocket acceptedSocket = acceptNative(timeout);
- mSocketState = SocketState.CONNECTED;
- return acceptedSocket;
- } finally {
- mLock.readLock().unlock();
+ IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
+ if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
+ mPfd = bluetoothProxy.connectSocket(mDevice, mType,
+ mUuid, mPort, getSecurityFlags());
+ synchronized(this)
+ {
+ Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
+ if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
+ if (mPfd == null) throw new IOException("bt socket connect failed");
+ FileDescriptor fd = mPfd.getFileDescriptor();
+ mSocket = new LocalSocket(fd);
+ mSocketIS = mSocket.getInputStream();
+ mSocketOS = mSocket.getOutputStream();
+ }
+ int channel = readInt(mSocketIS);
+ if (channel <= 0)
+ throw new IOException("bt socket connect failed");
+ mPort = channel;
+ waitSocketSignal(mSocketIS);
+ synchronized(this)
+ {
+ if (mSocketState == SocketState.CLOSED)
+ throw new IOException("bt socket closed");
+ mSocketState = SocketState.CONNECTED;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
}
}
- /*package*/ int available() throws IOException {
- mLock.readLock().lock();
+ /**
+ * Currently returns unix errno instead of throwing IOException,
+ * so that BluetoothAdapter can check the error code for EADDRINUSE
+ */
+ /*package*/ int bindListen() {
+ int ret;
+ if (mSocketState == SocketState.CLOSED) return EBADFD;
+ IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
+ if (bluetoothProxy == null) {
+ Log.e(TAG, "bindListen fail, reason: bluetooth is off");
+ return -1;
+ }
try {
- if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
- return availableNative();
- } finally {
- mLock.readLock().unlock();
+ mPfd = bluetoothProxy.createSocketChannel(mType, mServiceName,
+ mUuid, mPort, getSecurityFlags());
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ // TODO(BT) right error code?
+ return -1;
}
- }
- /*package*/ int read(byte[] b, int offset, int length) throws IOException {
- mLock.readLock().lock();
+ // read out port number
try {
- if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
- return readNative(b, offset, length);
- } finally {
- mLock.readLock().unlock();
+ synchronized(this) {
+ Log.d(TAG, "bindListen(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
+ if(mSocketState != SocketState.INIT) return EBADFD;
+ if(mPfd == null) return -1;
+ FileDescriptor fd = mPfd.getFileDescriptor();
+ Log.d(TAG, "bindListen(), new LocalSocket ");
+ mSocket = new LocalSocket(fd);
+ Log.d(TAG, "bindListen(), new LocalSocket.getInputStream() ");
+ mSocketIS = mSocket.getInputStream();
+ mSocketOS = mSocket.getOutputStream();
+ }
+ Log.d(TAG, "bindListen(), readInt mSocketIS: " + mSocketIS);
+ int channel = readInt(mSocketIS);
+ synchronized(this) {
+ if(mSocketState == SocketState.INIT)
+ mSocketState = SocketState.LISTENING;
+ }
+ Log.d(TAG, "channel: " + channel);
+ if (mPort == -1) {
+ mPort = channel;
+ } // else ASSERT(mPort == channel)
+ ret = 0;
+ } catch (IOException e) {
+ Log.e(TAG, "bindListen, fail to get port number, exception: " + e);
+ return -1;
}
+ return ret;
}
- /*package*/ int write(byte[] b, int offset, int length) throws IOException {
- mLock.readLock().lock();
- try {
- if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
- return writeNative(b, offset, length);
- } finally {
- mLock.readLock().unlock();
+ /*package*/ BluetoothSocket accept(int timeout) throws IOException {
+ BluetoothSocket acceptedSocket;
+ if (mSocketState != SocketState.LISTENING) throw new IOException("bt socket is not in listen state");
+ // TODO(BT) wait on an incoming connection
+ String RemoteAddr = waitSocketSignal(mSocketIS);
+ synchronized(this)
+ {
+ if (mSocketState != SocketState.LISTENING)
+ throw new IOException("bt socket is not in listen state");
+ acceptedSocket = acceptSocket(RemoteAddr);
+ //quick drop the reference of the file handle
}
+ // TODO(BT) rfcomm socket only supports one connection, return this?
+ // return this;
+ return acceptedSocket;
}
- private native void initSocketNative() throws IOException;
- private native void initSocketFromFdNative(int fd) throws IOException;
- private native void connectNative() throws IOException;
- private native int bindListenNative();
- private native BluetoothSocket acceptNative(int timeout) throws IOException;
- private native int availableNative() throws IOException;
- private native int readNative(byte[] b, int offset, int length) throws IOException;
- private native int writeNative(byte[] b, int offset, int length) throws IOException;
- private native void abortNative() throws IOException;
- private native void destroyNative() throws IOException;
- /**
- * Throws an IOException for given posix errno. Done natively so we can
- * use strerr to convert to string error.
- */
- /*package*/ native void throwErrnoNative(int errno) throws IOException;
+ /*package*/ int available() throws IOException {
+ Log.d(TAG, "available: " + mSocketIS);
+ return mSocketIS.available();
+ }
- /**
- * Helper to perform blocking SDP lookup.
- */
- private static class SdpHelper extends IBluetoothCallback.Stub {
- private final IBluetooth service;
- private final ParcelUuid uuid;
- private final BluetoothDevice device;
- private int channel;
- private boolean canceled;
- public SdpHelper(BluetoothDevice device, ParcelUuid uuid) {
- service = BluetoothDevice.getService();
- this.device = device;
- this.uuid = uuid;
- canceled = false;
- }
- /**
- * Returns the RFCOMM channel for the UUID, or throws IOException
- * on failure.
- */
- public synchronized int doSdp() throws IOException {
- if (canceled) throw new IOException("Service discovery canceled");
- channel = -1;
+ /*package*/ int read(byte[] b, int offset, int length) throws IOException {
- boolean inProgress = false;
- try {
- inProgress = service.fetchRemoteUuids(device.getAddress(), uuid, this);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
+ Log.d(TAG, "read in: " + mSocketIS + " len: " + length);
+ int ret = mSocketIS.read(b, offset, length);
+ if(ret < 0)
+ throw new IOException("bt socket closed, read return: " + ret);
+ Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret);
+ return ret;
+ }
- if (!inProgress) throw new IOException("Unable to start Service Discovery");
+ /*package*/ int write(byte[] b, int offset, int length) throws IOException {
- try {
- /* 12 second timeout as a precaution - onRfcommChannelFound
- * should always occur before the timeout */
- wait(12000); // block
+ Log.d(TAG, "write: " + mSocketOS + " length: " + length);
+ mSocketOS.write(b, offset, length);
+ // There is no good way to confirm since the entire process is asynchronous anyway
+ Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
+ return length;
+ }
- } catch (InterruptedException e) {}
+ @Override
+ public void close() throws IOException {
+ Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState);
+ if(mSocketState == SocketState.CLOSED)
+ return;
+ else
+ {
+ synchronized(this)
+ {
+ if(mSocketState == SocketState.CLOSED)
+ return;
+ mSocketState = SocketState.CLOSED;
+ Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS +
+ ", mSocketOS: " + mSocketOS + "mSocket: " + mSocket);
+ if(mSocket != null) {
+ Log.d(TAG, "Closing mSocket: " + mSocket);
+ mSocket.shutdownInput();
+ mSocket.shutdownOutput();
+ mSocket.close();
+ mSocket = null;
+ }
+ if(mPfd != null)
+ mPfd.detachFd();
+ }
+ }
+ // TODO(BT) unbind proxy,
+ }
- if (canceled) throw new IOException("Service discovery canceled");
- if (channel < 1) throw new IOException("Service discovery failed");
+ /*package */ void removeChannel() {
+ }
- return channel;
- }
- /** Object cannot be re-used after calling cancel() */
- public synchronized void cancel() {
- if (!canceled) {
- canceled = true;
- channel = -1;
- notifyAll(); // unblock
- }
- }
- public synchronized void onRfcommChannelFound(int channel) {
- if (!canceled) {
- this.channel = channel;
- notifyAll(); // unblock
- }
+ /*package */ int getPort() {
+ return mPort;
+ }
+ private String convertAddr(final byte[] addr) {
+ return String.format("%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 {
+ byte [] sig = new byte[SOCK_SIGNAL_SIZE];
+ int ret = readAll(is, sig);
+ Log.d(TAG, "waitSocketSignal read 16 bytes signal ret: " + ret);
+ ByteBuffer bb = ByteBuffer.wrap(sig);
+ bb.order(ByteOrder.nativeOrder());
+ int size = bb.getShort();
+ byte [] addr = new byte[6];
+ bb.get(addr);
+ int channel = bb.getInt();
+ int status = bb.getInt();
+ String RemoteAddr = convertAddr(addr);
+ Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: "
+ + RemoteAddr + ", channel: " + channel + ", status: " + status);
+ if(status != 0)
+ throw new IOException("Connection failure, status: " + status);
+ return RemoteAddr;
+ }
+ private int readAll(InputStream is, byte[] b) throws IOException {
+ int left = b.length;
+ while(left > 0) {
+ int ret = is.read(b, b.length - left, left);
+ if(ret <= 0)
+ throw new IOException("read failed, socket might closed, read ret: " + ret);
+ left -= ret;
+ if(left != 0)
+ Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) +
+ ", expect size: " + b.length);
}
+ return b.length;
+ }
+
+ private int readInt(InputStream is) throws IOException {
+ byte[] ibytes = new byte[4];
+ int ret = readAll(is, ibytes);
+ Log.d(TAG, "inputStream.read ret: " + ret);
+ ByteBuffer bb = ByteBuffer.wrap(ibytes);
+ bb.order(ByteOrder.nativeOrder());
+ return bb.getInt();
}
}
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
index 83d1bda..30406e9 100644
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -16,6 +16,9 @@
package android.bluetooth;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.os.INetworkManagementService;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.DhcpInfoInternal;
@@ -28,6 +31,11 @@ import android.net.NetworkUtils;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
+import java.net.InterfaceAddress;
+import android.net.LinkAddress;
+import android.net.RouteInfo;
+import java.net.Inet4Address;
+import android.os.SystemProperties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@@ -54,9 +62,8 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker {
private NetworkInfo mNetworkInfo;
private BluetoothPan mBluetoothPan;
- private BluetoothDevice mDevice;
private static String mIface;
-
+ private Thread mDhcpThread;
/* For sending events to connectivity service handler */
private Handler mCsHandler;
private Context mContext;
@@ -92,8 +99,10 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker {
* Begin monitoring connectivity
*/
public void startMonitoring(Context context, Handler target) {
+ Log.d(TAG, "startMonitoring: target: " + target);
mContext = context;
mCsHandler = target;
+ Log.d(TAG, "startMonitoring: mCsHandler: " + mCsHandler);
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null) {
adapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.PAN);
@@ -124,6 +133,11 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker {
return true;
}
+ @Override
+ public void captivePortalCheckComplete() {
+ // not implemented
+ }
+
/**
* Re-enable connectivity to a network after a {@link #teardown()}.
*/
@@ -259,38 +273,91 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker {
return "net.tcp.buffersize.wifi";
}
+ private static short countPrefixLength(byte [] mask) {
+ short count = 0;
+ for (byte b : mask) {
+ for (int i = 0; i < 8; ++i) {
+ if ((b & (1 << i)) != 0) {
+ ++count;
+ }
+ }
+ }
+ return count;
+ }
+
- public synchronized void startReverseTether(String iface, BluetoothDevice device) {
+ private boolean readLinkProperty(String iface) {
+ String DhcpPrefix = "dhcp." + iface + ".";
+ String ip = SystemProperties.get(DhcpPrefix + "ipaddress");
+ String dns1 = SystemProperties.get(DhcpPrefix + "dns1");
+ String dns2 = SystemProperties.get(DhcpPrefix + "dns2");
+ String gateway = SystemProperties.get(DhcpPrefix + "gateway");
+ String mask = SystemProperties.get(DhcpPrefix + "mask");
+ if(ip.isEmpty() || gateway.isEmpty()) {
+ Log.e(TAG, "readLinkProperty, ip: " + ip + ", gateway: " + gateway + ", can not be empty");
+ return false;
+ }
+ int PrefixLen = countPrefixLength(NetworkUtils.numericToInetAddress(mask).getAddress());
+ mLinkProperties.addLinkAddress(new LinkAddress(NetworkUtils.numericToInetAddress(ip), PrefixLen));
+ RouteInfo ri = new RouteInfo(NetworkUtils.numericToInetAddress(gateway));
+ mLinkProperties.addRoute(ri);
+ if(!dns1.isEmpty())
+ mLinkProperties.addDns(NetworkUtils.numericToInetAddress(dns1));
+ if(!dns2.isEmpty())
+ mLinkProperties.addDns(NetworkUtils.numericToInetAddress(dns2));
+ mLinkProperties.setInterfaceName(iface);
+ return true;
+ }
+ public synchronized void startReverseTether(String iface) {
mIface = iface;
- mDevice = device;
- Thread dhcpThread = new Thread(new Runnable() {
+ Log.d(TAG, "startReverseTether mCsHandler: " + mCsHandler);
+ mDhcpThread = new Thread(new Runnable() {
public void run() {
//TODO(): Add callbacks for failure and success case.
//Currently this thread runs independently.
- DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
- if (!NetworkUtils.runDhcp(mIface, dhcpInfoInternal)) {
- Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
- return;
+ Log.d(TAG, "startReverseTether mCsHandler: " + mCsHandler);
+ String DhcpResultName = "dhcp." + mIface + ".result";;
+ String result = "";
+ Log.d(TAG, "waiting for change of sys prop dhcp result: " + DhcpResultName);
+ for(int i = 0; i < 30*5; i++) {
+ try { Thread.sleep(200); } catch (InterruptedException ie) { return;}
+ result = SystemProperties.get(DhcpResultName);
+ Log.d(TAG, "read " + DhcpResultName + ": " + result);
+ if(result.equals("failed")) {
+ Log.e(TAG, "startReverseTether, failed to start dhcp service");
+ return;
+ }
+ if(result.equals("ok")) {
+ Log.d(TAG, "startReverseTether, dhcp resut: " + result);
+ if(readLinkProperty(mIface)) {
+
+ mNetworkInfo.setIsAvailable(true);
+ mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
+
+ Log.d(TAG, "startReverseTether mCsHandler: " + mCsHandler);
+ if(mCsHandler != null) {
+ Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
+ msg.sendToTarget();
+
+ msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
+ msg.sendToTarget();
+ }
+ }
+ return;
+ }
}
- mLinkProperties = dhcpInfoInternal.makeLinkProperties();
- mLinkProperties.setInterfaceName(mIface);
-
- mNetworkInfo.setIsAvailable(true);
- mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
-
- Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
- msg.sendToTarget();
-
- msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
- msg.sendToTarget();
+ Log.d(TAG, "startReverseTether, dhcp failed, resut: " + result);
}
});
- dhcpThread.start();
+ mDhcpThread.start();
}
- public synchronized void stopReverseTether(String iface) {
- NetworkUtils.stopDhcp(iface);
-
+ public synchronized void stopReverseTether() {
+ //NetworkUtils.stopDhcp(iface);
+ if(mDhcpThread != null && mDhcpThread.isAlive()) {
+ mDhcpThread.interrupt();
+ try { mDhcpThread.join(); } catch (InterruptedException ie) { return; }
+ }
mLinkProperties.clear();
mNetworkInfo.setIsAvailable(false);
mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
diff --git a/core/java/android/bluetooth/HeadsetBase.java b/core/java/android/bluetooth/HeadsetBase.java
deleted file mode 100644
index 9ef2eb5..0000000
--- a/core/java/android/bluetooth/HeadsetBase.java
+++ /dev/null
@@ -1,298 +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.bluetooth;
-
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.util.Log;
-
-/**
- * The Android Bluetooth API is not finalized, and *will* change. Use at your
- * own risk.
- *
- * The base RFCOMM (service) connection for a headset or handsfree device.
- *
- * In the future this class will be removed.
- *
- * @hide
- */
-public final class HeadsetBase {
- private static final String TAG = "Bluetooth HeadsetBase";
- private static final boolean DBG = false;
-
- public static final int RFCOMM_DISCONNECTED = 1;
-
- public static final int DIRECTION_INCOMING = 1;
- public static final int DIRECTION_OUTGOING = 2;
-
- private static int sAtInputCount = 0; /* TODO: Consider not using a static variable */
-
- private final BluetoothAdapter mAdapter;
- private final BluetoothDevice mRemoteDevice;
- private final String mAddress; // for native code
- private final int mRfcommChannel;
- private int mNativeData;
- private Thread mEventThread;
- private volatile boolean mEventThreadInterrupted;
- private Handler mEventThreadHandler;
- private int mTimeoutRemainingMs;
- private final int mDirection;
- private final long mConnectTimestamp;
-
- protected AtParser mAtParser;
-
- private WakeLock mWakeLock; // held while processing an AT command
-
- private native static void classInitNative();
- static {
- classInitNative();
- }
-
- protected void finalize() throws Throwable {
- try {
- cleanupNativeDataNative();
- releaseWakeLock();
- } finally {
- super.finalize();
- }
- }
-
- private native void cleanupNativeDataNative();
-
- public HeadsetBase(PowerManager pm, BluetoothAdapter adapter,
- BluetoothDevice device, int rfcommChannel) {
- mDirection = DIRECTION_OUTGOING;
- mConnectTimestamp = System.currentTimeMillis();
- mAdapter = adapter;
- mRemoteDevice = device;
- mAddress = device.getAddress();
- mRfcommChannel = rfcommChannel;
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase");
- mWakeLock.setReferenceCounted(false);
- initializeAtParser();
- // Must be called after this.mAddress is set.
- initializeNativeDataNative(-1);
- }
-
- /* Create from an existing rfcomm connection */
- public HeadsetBase(PowerManager pm, BluetoothAdapter adapter,
- BluetoothDevice device,
- int socketFd, int rfcommChannel, Handler handler) {
- mDirection = DIRECTION_INCOMING;
- mConnectTimestamp = System.currentTimeMillis();
- mAdapter = adapter;
- mRemoteDevice = device;
- mAddress = device.getAddress();
- mRfcommChannel = rfcommChannel;
- mEventThreadHandler = handler;
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase");
- mWakeLock.setReferenceCounted(false);
- initializeAtParser();
- // Must be called after this.mAddress is set.
- initializeNativeDataNative(socketFd);
- }
-
- private native void initializeNativeDataNative(int socketFd);
-
- /* Process an incoming AT command line
- */
- protected void handleInput(String input) {
- acquireWakeLock();
- long timestamp;
-
- synchronized(HeadsetBase.class) {
- if (sAtInputCount == Integer.MAX_VALUE) {
- sAtInputCount = 0;
- } else {
- sAtInputCount++;
- }
- }
-
- if (DBG) timestamp = System.currentTimeMillis();
- AtCommandResult result = mAtParser.process(input);
- if (DBG) Log.d(TAG, "Processing " + input + " took " +
- (System.currentTimeMillis() - timestamp) + " ms");
-
- if (result.getResultCode() == AtCommandResult.ERROR) {
- Log.i(TAG, "Error processing <" + input + ">");
- }
-
- sendURC(result.toString());
-
- releaseWakeLock();
- }
-
- /**
- * Register AT commands that are common to all Headset / Handsets. This
- * function is called by the HeadsetBase constructor.
- */
- protected void initializeAtParser() {
- mAtParser = new AtParser();
-
- //TODO(): Get rid of this as there are no parsers registered. But because of dependencies
- // it needs to be done as part of refactoring HeadsetBase and BluetoothHandsfree
- }
-
- public AtParser getAtParser() {
- return mAtParser;
- }
-
- public void startEventThread() {
- mEventThread =
- new Thread("HeadsetBase Event Thread") {
- public void run() {
- int last_read_error;
- while (!mEventThreadInterrupted) {
- String input = readNative(500);
- if (input != null) {
- handleInput(input);
- } else {
- last_read_error = getLastReadStatusNative();
- if (last_read_error != 0) {
- Log.i(TAG, "headset read error " + last_read_error);
- if (mEventThreadHandler != null) {
- mEventThreadHandler.obtainMessage(RFCOMM_DISCONNECTED)
- .sendToTarget();
- }
- disconnectNative();
- break;
- }
- }
- }
- }
- };
- mEventThreadInterrupted = false;
- mEventThread.start();
- }
-
- private native String readNative(int timeout_ms);
- private native int getLastReadStatusNative();
-
- private void stopEventThread() {
- mEventThreadInterrupted = true;
- mEventThread.interrupt();
- try {
- mEventThread.join();
- } catch (java.lang.InterruptedException e) {
- // FIXME: handle this,
- }
- mEventThread = null;
- }
-
- public boolean connect(Handler handler) {
- if (mEventThread == null) {
- if (!connectNative()) return false;
- mEventThreadHandler = handler;
- }
- return true;
- }
- private native boolean connectNative();
-
- /*
- * Returns true when either the asynchronous connect is in progress, or
- * the connect is complete. Call waitForAsyncConnect() to find out whether
- * the connect is actually complete, or disconnect() to cancel.
- */
-
- public boolean connectAsync() {
- int ret = connectAsyncNative();
- return (ret == 0) ? true : false;
- }
- private native int connectAsyncNative();
-
- public int getRemainingAsyncConnectWaitingTimeMs() {
- return mTimeoutRemainingMs;
- }
-
- /*
- * Returns 1 when an async connect is complete, 0 on timeout, and -1 on
- * error. On error, handler will be called, and you need to re-initiate
- * the async connect.
- */
- public int waitForAsyncConnect(int timeout_ms, Handler handler) {
- int res = waitForAsyncConnectNative(timeout_ms);
- if (res > 0) {
- mEventThreadHandler = handler;
- }
- return res;
- }
- private native int waitForAsyncConnectNative(int timeout_ms);
-
- public void disconnect() {
- if (mEventThread != null) {
- stopEventThread();
- }
- disconnectNative();
- }
- private native void disconnectNative();
-
-
- /*
- * Note that if a remote side disconnects, this method will still return
- * true until disconnect() is called. You know when a remote side
- * disconnects because you will receive the intent
- * IBluetoothService.REMOTE_DEVICE_DISCONNECTED_ACTION. If, when you get
- * this intent, method isConnected() returns true, you know that the
- * disconnect was initiated by the remote device.
- */
-
- public boolean isConnected() {
- return mEventThread != null;
- }
-
- public BluetoothDevice getRemoteDevice() {
- return mRemoteDevice;
- }
-
- public int getDirection() {
- return mDirection;
- }
-
- public long getConnectTimestamp() {
- return mConnectTimestamp;
- }
-
- public synchronized boolean sendURC(String urc) {
- if (urc.length() > 0) {
- boolean ret = sendURCNative(urc);
- return ret;
- }
- return true;
- }
- private native boolean sendURCNative(String urc);
-
- private synchronized void acquireWakeLock() {
- if (!mWakeLock.isHeld()) {
- mWakeLock.acquire();
- }
- }
-
- private synchronized void releaseWakeLock() {
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
-
- public static int getAtInputCount() {
- return sAtInputCount;
- }
-
- private static void log(String msg) {
- Log.d(TAG, msg);
- }
-}
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 6075363..d016c26 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -18,9 +18,7 @@ package android.bluetooth;
import android.bluetooth.IBluetoothCallback;
import android.bluetooth.IBluetoothStateChangeCallback;
-import android.bluetooth.IBluetoothHealthCallback;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHealthAppConfiguration;
import android.os.ParcelUuid;
import android.os.ParcelFileDescriptor;
@@ -32,15 +30,15 @@ import android.os.ParcelFileDescriptor;
interface IBluetooth
{
boolean isEnabled();
- int getBluetoothState();
+ int getState();
boolean enable();
boolean enableNoAutoConnect();
- boolean disable(boolean persistSetting);
+ boolean disable();
String getAddress();
- String getName();
- boolean setName(in String name);
ParcelUuid[] getUuids();
+ boolean setName(in String name);
+ String getName();
int getScanMode();
boolean setScanMode(int mode, int duration);
@@ -51,77 +49,34 @@ interface IBluetooth
boolean startDiscovery();
boolean cancelDiscovery();
boolean isDiscovering();
- byte[] readOutOfBandData();
int getAdapterConnectionState();
int getProfileConnectionState(int profile);
- boolean changeApplicationBluetoothState(boolean on,
- in IBluetoothStateChangeCallback callback, in
- IBinder b);
-
- boolean createBond(in String address);
- boolean createBondOutOfBand(in String address, in byte[] hash, in byte[] randomizer);
- boolean cancelBondProcess(in String address);
- boolean removeBond(in String address);
- String[] listBonds();
- int getBondState(in String address);
- boolean setDeviceOutOfBandData(in String address, in byte[] hash, in byte[] randomizer);
- String getRemoteName(in String address);
- String getRemoteAlias(in String address);
- boolean setRemoteAlias(in String address, in String name);
- int getRemoteClass(in String address);
- ParcelUuid[] getRemoteUuids(in String address);
- boolean fetchRemoteUuids(in String address, in ParcelUuid uuid, in IBluetoothCallback callback);
- int getRemoteServiceChannel(in String address, in ParcelUuid uuid);
+ BluetoothDevice[] getBondedDevices();
+ boolean createBond(in BluetoothDevice device);
+ boolean cancelBondProcess(in BluetoothDevice device);
+ boolean removeBond(in BluetoothDevice device);
+ int getBondState(in BluetoothDevice device);
- boolean setPin(in String address, in byte[] pin);
- boolean setPasskey(in String address, int passkey);
- boolean setPairingConfirmation(in String address, boolean confirm);
- boolean setRemoteOutOfBandData(in String addres);
- boolean cancelPairingUserInput(in String address);
+ String getRemoteName(in BluetoothDevice device);
+ String getRemoteAlias(in BluetoothDevice device);
+ boolean setRemoteAlias(in BluetoothDevice device, in String name);
+ int getRemoteClass(in BluetoothDevice device);
+ ParcelUuid[] getRemoteUuids(in BluetoothDevice device);
+ boolean fetchRemoteUuids(in BluetoothDevice device);
- boolean setTrust(in String address, in boolean value);
- boolean getTrustState(in String address);
- boolean isBluetoothDock(in String address);
+ boolean setPin(in BluetoothDevice device, boolean accept, int len, in byte[] pinCode);
+ boolean setPasskey(in BluetoothDevice device, boolean accept, int len, in byte[]
+ passkey);
+ boolean setPairingConfirmation(in BluetoothDevice device, boolean accept);
- int addRfcommServiceRecord(in String serviceName, in ParcelUuid uuid, int channel, IBinder b);
- void removeServiceRecord(int handle);
- boolean allowIncomingProfileConnect(in BluetoothDevice device, boolean value);
-
- boolean connectHeadset(String address);
- boolean disconnectHeadset(String address);
- boolean notifyIncomingConnection(String address, boolean rejected);
-
- // HID profile APIs
- boolean connectInputDevice(in BluetoothDevice device);
- boolean disconnectInputDevice(in BluetoothDevice device);
- List<BluetoothDevice> getConnectedInputDevices();
- List<BluetoothDevice> getInputDevicesMatchingConnectionStates(in int[] states);
- int getInputDeviceConnectionState(in BluetoothDevice device);
- boolean setInputDevicePriority(in BluetoothDevice device, int priority);
- int getInputDevicePriority(in BluetoothDevice device);
-
- boolean isTetheringOn();
- void setBluetoothTethering(boolean value);
- int getPanDeviceConnectionState(in BluetoothDevice device);
- List<BluetoothDevice> getConnectedPanDevices();
- List<BluetoothDevice> getPanDevicesMatchingConnectionStates(in int[] states);
- boolean connectPanDevice(in BluetoothDevice device);
- boolean disconnectPanDevice(in BluetoothDevice device);
+ void sendConnectionStateChange(in BluetoothDevice device, int profile, int state, int prevState);
- // HDP profile APIs
- boolean registerAppConfiguration(in BluetoothHealthAppConfiguration config,
- in IBluetoothHealthCallback callback);
- boolean unregisterAppConfiguration(in BluetoothHealthAppConfiguration config);
- boolean connectChannelToSource(in BluetoothDevice device, in BluetoothHealthAppConfiguration config);
- boolean connectChannelToSink(in BluetoothDevice device, in BluetoothHealthAppConfiguration config,
- int channelType);
- boolean disconnectChannel(in BluetoothDevice device, in BluetoothHealthAppConfiguration config, int id);
- ParcelFileDescriptor getMainChannelFd(in BluetoothDevice device, in BluetoothHealthAppConfiguration config);
- List<BluetoothDevice> getConnectedHealthDevices();
- List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(in int[] states);
- int getHealthDeviceConnectionState(in BluetoothDevice device);
+ void registerCallback(in IBluetoothCallback callback);
+ void unregisterCallback(in IBluetoothCallback callback);
- void sendConnectionStateChange(in BluetoothDevice device, int profile, int state, int prevState);
+ // 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);
}
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
index 444dd1e..1f10998 100644
--- a/core/java/android/bluetooth/IBluetoothA2dp.aidl
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -33,12 +33,4 @@ interface IBluetoothA2dp {
boolean setPriority(in BluetoothDevice device, int priority);
int getPriority(in BluetoothDevice device);
boolean isA2dpPlaying(in BluetoothDevice device);
-
- // Internal APIs
- boolean suspendSink(in BluetoothDevice device);
- boolean resumeSink(in BluetoothDevice device);
- boolean connectSinkInternal(in BluetoothDevice device);
- boolean disconnectSinkInternal(in BluetoothDevice device);
- boolean allowIncomingConnect(in BluetoothDevice device, boolean value);
-
}
diff --git a/core/java/android/bluetooth/IBluetoothCallback.aidl b/core/java/android/bluetooth/IBluetoothCallback.aidl
index 8edb3f4..e280978 100644
--- a/core/java/android/bluetooth/IBluetoothCallback.aidl
+++ b/core/java/android/bluetooth/IBluetoothCallback.aidl
@@ -23,5 +23,6 @@ package android.bluetooth;
*/
interface IBluetoothCallback
{
- void onRfcommChannelFound(int channel);
+ //void onRfcommChannelFound(int channel);
+ void onBluetoothStateChange(int prevState, int newState);
}
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index ec00527..fc7627a 100644
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -40,15 +40,17 @@ interface IBluetoothHeadset {
int getBatteryUsageHint(in BluetoothDevice device);
// Internal functions, not be made public
- boolean createIncomingConnect(in BluetoothDevice device);
boolean acceptIncomingConnect(in BluetoothDevice device);
boolean rejectIncomingConnect(in BluetoothDevice device);
- boolean cancelConnectThread();
- boolean connectHeadsetInternal(in BluetoothDevice device);
- boolean disconnectHeadsetInternal(in BluetoothDevice device);
- boolean setAudioState(in BluetoothDevice device, int state);
int getAudioState(in BluetoothDevice device);
+ boolean isAudioOn();
+ boolean connectAudio();
+ boolean disconnectAudio();
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/IBluetoothHeadsetPhone.aidl b/core/java/android/bluetooth/IBluetoothHeadsetPhone.aidl
new file mode 100644
index 0000000..163e4e2
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothHeadsetPhone.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ */
+
+package android.bluetooth;
+
+/**
+ * API for Bluetooth Headset Phone Service in phone app
+ *
+ * {@hide}
+ */
+interface IBluetoothHeadsetPhone {
+ // Internal functions, not be made public
+ boolean answerCall();
+ boolean hangupCall();
+ boolean sendDtmf(int dtmf);
+ boolean processChld(int chld);
+ String getNetworkOperator();
+ String getSubscriberNumber();
+ boolean listCurrentCalls();
+ boolean queryPhoneState();
+
+ // Internal for phone app to call
+ void updateBtHandsfreeAfterRadioTechnologyChange();
+ void cdmaSwapSecondCallState();
+ void cdmaSetSecondCallState(boolean state);
+}
diff --git a/core/java/android/bluetooth/IBluetoothHealth.aidl b/core/java/android/bluetooth/IBluetoothHealth.aidl
new file mode 100644
index 0000000..e741da4
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothHealth.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHealthAppConfiguration;
+import android.bluetooth.IBluetoothHealthCallback;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * API for Bluetooth Health service
+ *
+ * {@hide}
+ */
+interface IBluetoothHealth
+{
+ boolean registerAppConfiguration(in BluetoothHealthAppConfiguration config,
+ in IBluetoothHealthCallback callback);
+ boolean unregisterAppConfiguration(in BluetoothHealthAppConfiguration config);
+ boolean connectChannelToSource(in BluetoothDevice device, in BluetoothHealthAppConfiguration config);
+ boolean connectChannelToSink(in BluetoothDevice device, in BluetoothHealthAppConfiguration config,
+ int channelType);
+ boolean disconnectChannel(in BluetoothDevice device, in BluetoothHealthAppConfiguration config, int id);
+ ParcelFileDescriptor getMainChannelFd(in BluetoothDevice device, in BluetoothHealthAppConfiguration config);
+ List<BluetoothDevice> getConnectedHealthDevices();
+ List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(in int[] states);
+ int getHealthDeviceConnectionState(in BluetoothDevice device);
+}
diff --git a/core/java/android/bluetooth/IBluetoothInputDevice.aidl b/core/java/android/bluetooth/IBluetoothInputDevice.aidl
new file mode 100755
index 0000000..23e6d50
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothInputDevice.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ */
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * API for Bluetooth HID service
+ *
+ * {@hide}
+ */
+interface IBluetoothInputDevice {
+ // Public API
+ boolean connect(in BluetoothDevice device);
+ boolean disconnect(in BluetoothDevice device);
+ List<BluetoothDevice> getConnectedDevices();
+ List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
+ int getConnectionState(in BluetoothDevice device);
+ boolean setPriority(in BluetoothDevice device, int priority);
+ int getPriority(in BluetoothDevice device);
+ /**
+ * @hide
+ */
+ boolean getProtocolMode(in BluetoothDevice device);
+ /**
+ * @hide
+ */
+ boolean virtualUnplug(in BluetoothDevice device);
+ /**
+ * @hide
+ */
+ boolean setProtocolMode(in BluetoothDevice device, int protocolMode);
+ /**
+ * @hide
+ */
+ boolean getReport(in BluetoothDevice device, byte reportType, byte reportId, int bufferSize);
+ /**
+ * @hide
+ */
+ boolean setReport(in BluetoothDevice device, byte reportType, String report);
+ /**
+ * @hide
+ */
+ boolean sendData(in BluetoothDevice device, String report);
+}
diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl
new file mode 100755
index 0000000..de8fe91
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothManager.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.IBluetooth;
+import android.bluetooth.IBluetoothManagerCallback;
+import android.bluetooth.IBluetoothStateChangeCallback;
+
+/**
+ * System private API for talking with the Bluetooth service.
+ *
+ * {@hide}
+ */
+interface IBluetoothManager
+{
+ IBluetooth registerAdapter(in IBluetoothManagerCallback callback);
+ void unregisterAdapter(in IBluetoothManagerCallback callback);
+ void registerStateChangeCallback(in IBluetoothStateChangeCallback callback);
+ void unregisterStateChangeCallback(in IBluetoothStateChangeCallback callback);
+ boolean isEnabled();
+ boolean enable();
+ boolean enableNoAutoConnect();
+ boolean disable(boolean persist);
+
+ String getAddress();
+ String getName();
+}
diff --git a/core/java/android/bluetooth/IBluetoothManagerCallback.aidl b/core/java/android/bluetooth/IBluetoothManagerCallback.aidl
new file mode 100644
index 0000000..3e795ea
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothManagerCallback.aidl
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.IBluetooth;
+
+/**
+ * API for Communication between BluetoothAdapter and BluetoothManager
+ *
+ * {@hide}
+ */
+interface IBluetoothManagerCallback {
+ void onBluetoothServiceUp(in IBluetooth bluetoothService);
+ void onBluetoothServiceDown();
+} \ No newline at end of file
diff --git a/core/java/android/bluetooth/IBluetoothPan.aidl b/core/java/android/bluetooth/IBluetoothPan.aidl
new file mode 100644
index 0000000..b91bd7d
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothPan.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ */
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * API for Bluetooth Pan service
+ *
+ * {@hide}
+ */
+interface IBluetoothPan {
+ // Public API
+ boolean isTetheringOn();
+ void setBluetoothTethering(boolean value);
+ boolean connect(in BluetoothDevice device);
+ boolean disconnect(in BluetoothDevice device);
+ List<BluetoothDevice> getConnectedDevices();
+ List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
+ int getConnectionState(in BluetoothDevice device);
+}
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 446f1af..1500b00 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -237,16 +237,17 @@ public abstract class BroadcastReceiver {
final boolean mOrderedHint;
final boolean mInitialStickyHint;
final IBinder mToken;
+ final int mSendingUser;
int mResultCode;
String mResultData;
Bundle mResultExtras;
boolean mAbortBroadcast;
boolean mFinished;
-
+
/** @hide */
public PendingResult(int resultCode, String resultData, Bundle resultExtras,
- int type, boolean ordered, boolean sticky, IBinder token) {
+ int type, boolean ordered, boolean sticky, IBinder token, int userId) {
mResultCode = resultCode;
mResultData = resultData;
mResultExtras = resultExtras;
@@ -254,6 +255,7 @@ public abstract class BroadcastReceiver {
mOrderedHint = ordered;
mInitialStickyHint = sticky;
mToken = token;
+ mSendingUser = userId;
}
/**
@@ -425,7 +427,12 @@ public abstract class BroadcastReceiver {
}
}
}
-
+
+ /** @hide */
+ public int getSendingUserId() {
+ return mSendingUser;
+ }
+
void checkSynchronousHint() {
// Note that we don't assert when receiving the initial sticky value,
// since that may have come from an ordered broadcast. We'll catch
@@ -733,6 +740,11 @@ public abstract class BroadcastReceiver {
return mPendingResult;
}
+ /** @hide */
+ public int getSendingUserId() {
+ return mPendingResult.mSendingUser;
+ }
+
/**
* Control inclusion of debugging help for mismatched
* calls to {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 1866830..88f1a3d 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -563,7 +563,7 @@ public class ClipData implements Parcelable {
private String uriToHtml(String uri) {
StringBuilder builder = new StringBuilder(256);
builder.append("<a href=\"");
- builder.append(uri);
+ builder.append(Html.escapeHtml(uri));
builder.append("\">");
builder.append(Html.escapeHtml(uri));
builder.append("</a>");
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index b22179e..23d8f46 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -35,7 +35,7 @@ import android.os.OperationCanceledException;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
-import android.os.UserId;
+import android.os.UserHandle;
import android.util.Log;
import java.io.File;
@@ -279,7 +279,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
final int uid = Binder.getCallingUid();
String missingPerm = null;
- if (uid == mMyUid) {
+ if (UserHandle.isSameApp(uid, mMyUid)) {
return;
}
@@ -340,7 +340,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
final int uid = Binder.getCallingUid();
String missingPerm = null;
- if (uid == mMyUid) {
+ if (UserHandle.isSameApp(uid, mMyUid)) {
return;
}
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 5c315ce..204f963 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -231,6 +231,19 @@ public class ContentProviderClient {
}
}
+ /** See {@link ContentProvider#call(String, String, Bundle)} */
+ public Bundle call(String method, String arg, Bundle extras)
+ throws RemoteException {
+ try {
+ return mContentProvider.call(method, arg, extras);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ }
+ }
+
/**
* Call this to indicate to the system that the associated {@link ContentProvider} is no
* longer needed by this {@link ContentProviderClient}.
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 0a5a26a..4ab8272 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -39,6 +39,7 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
@@ -230,7 +231,8 @@ public abstract class ContentResolver {
}
try {
- String type = ActivityManagerNative.getDefault().getProviderMimeType(url);
+ String type = ActivityManagerNative.getDefault().getProviderMimeType(
+ url, UserHandle.myUserId());
return type;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
@@ -1217,9 +1219,16 @@ public abstract class ContentResolver {
public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
ContentObserver observer)
{
+ registerContentObserver(uri, notifyForDescendents, observer, UserHandle.getCallingUserId());
+ }
+
+ /** @hide - designated user version */
+ public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
+ ContentObserver observer, int userHandle)
+ {
try {
getContentService().registerContentObserver(uri, notifyForDescendents,
- observer.getContentObserver());
+ observer.getContentObserver(), userHandle);
} catch (RemoteException e) {
}
}
@@ -1274,10 +1283,21 @@ public abstract class ContentResolver {
* @see #requestSync(android.accounts.Account, String, android.os.Bundle)
*/
public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) {
+ notifyChange(uri, observer, syncToNetwork, UserHandle.getCallingUserId());
+ }
+
+ /**
+ * Notify registered observers within the designated user(s) that a row was updated.
+ *
+ * @hide
+ */
+ public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork,
+ int userHandle) {
try {
getContentService().notifyChange(
uri, observer == null ? null : observer.getContentObserver(),
- observer != null && observer.deliverSelfNotifications(), syncToNetwork);
+ observer != null && observer.deliverSelfNotifications(), syncToNetwork,
+ userHandle);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index 1a07504..0f6488a 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -17,6 +17,7 @@
package android.content;
import android.accounts.Account;
+import android.app.ActivityManager;
import android.database.IContentObserver;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
@@ -26,13 +27,14 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.UserId;
+import android.os.UserHandle;
import android.util.Log;
import android.util.SparseIntArray;
import android.Manifest;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -138,19 +140,49 @@ public final class ContentService extends IContentService.Stub {
getSyncManager();
}
- public void registerContentObserver(Uri uri, boolean notifyForDescendents,
- IContentObserver observer) {
+ /**
+ * Register a content observer tied to a specific user's view of the provider.
+ * @param userHandle the user whose view of the provider is to be observed. May be
+ * the calling user without requiring any permission, otherwise the caller needs to
+ * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and
+ * USER_CURRENT are properly handled; all other pseudousers are forbidden.
+ */
+ @Override
+ public void registerContentObserver(Uri uri, boolean notifyForDescendants,
+ IContentObserver observer, int userHandle) {
if (observer == null || uri == null) {
throw new IllegalArgumentException("You must pass a valid uri and observer");
}
+
+ final int callingUser = UserHandle.getCallingUserId();
+ if (callingUser != userHandle) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "no permission to observe other users' provider view");
+ }
+
+ if (userHandle < 0) {
+ if (userHandle == UserHandle.USER_CURRENT) {
+ userHandle = ActivityManager.getCurrentUser();
+ } else if (userHandle != UserHandle.USER_ALL) {
+ throw new InvalidParameterException("Bad user handle for registerContentObserver: "
+ + userHandle);
+ }
+ }
+
synchronized (mRootNode) {
- mRootNode.addObserverLocked(uri, observer, notifyForDescendents, mRootNode,
- Binder.getCallingUid(), Binder.getCallingPid());
+ mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
+ Binder.getCallingUid(), Binder.getCallingPid(), userHandle);
if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
- " with notifyForDescendents " + notifyForDescendents);
+ " with notifyForDescendants " + notifyForDescendants);
}
}
+ public void registerContentObserver(Uri uri, boolean notifyForDescendants,
+ IContentObserver observer) {
+ registerContentObserver(uri, notifyForDescendants, observer,
+ UserHandle.getCallingUserId());
+ }
+
public void unregisterContentObserver(IContentObserver observer) {
if (observer == null) {
throw new IllegalArgumentException("You must pass a valid observer");
@@ -161,14 +193,39 @@ public final class ContentService extends IContentService.Stub {
}
}
+ /**
+ * Notify observers of a particular user's view of the provider.
+ * @param userHandle the user whose view of the provider is to be notified. May be
+ * the calling user without requiring any permission, otherwise the caller needs to
+ * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and
+ * USER_CURRENT are properly interpreted; no other pseudousers are allowed.
+ */
+ @Override
public void notifyChange(Uri uri, IContentObserver observer,
- boolean observerWantsSelfNotifications, boolean syncToNetwork) {
+ boolean observerWantsSelfNotifications, boolean syncToNetwork,
+ int userHandle) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Notifying update of " + uri + " from observer " + observer
- + ", syncToNetwork " + syncToNetwork);
+ Log.v(TAG, "Notifying update of " + uri + " for user " + userHandle
+ + " from observer " + observer + ", syncToNetwork " + syncToNetwork);
+ }
+
+ // Notify for any user other than the caller's own requires permission.
+ final int callingUserHandle = UserHandle.getCallingUserId();
+ if (userHandle != callingUserHandle) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "no permission to notify other users");
+ }
+
+ // We passed the permission check; resolve pseudouser targets as appropriate
+ if (userHandle < 0) {
+ if (userHandle == UserHandle.USER_CURRENT) {
+ userHandle = ActivityManager.getCurrentUser();
+ } else if (userHandle != UserHandle.USER_ALL) {
+ throw new InvalidParameterException("Bad user handle for notifyChange: "
+ + userHandle);
+ }
}
- int userId = UserId.getCallingUserId();
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
long identityToken = clearCallingIdentity();
@@ -176,7 +233,7 @@ public final class ContentService extends IContentService.Stub {
ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
synchronized (mRootNode) {
mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
- calls);
+ userHandle, calls);
}
final int numCalls = calls.size();
for (int i=0; i<numCalls; i++) {
@@ -207,7 +264,7 @@ public final class ContentService extends IContentService.Stub {
if (syncToNetwork) {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
- syncManager.scheduleLocalSync(null /* all accounts */, userId,
+ syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle,
uri.getAuthority());
}
}
@@ -216,6 +273,12 @@ public final class ContentService extends IContentService.Stub {
}
}
+ public void notifyChange(Uri uri, IContentObserver observer,
+ boolean observerWantsSelfNotifications, boolean syncToNetwork) {
+ notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork,
+ UserHandle.getCallingUserId());
+ }
+
/**
* Hide this class since it is not part of api,
* but current unittest framework requires it to be public
@@ -236,7 +299,7 @@ public final class ContentService extends IContentService.Stub {
public void requestSync(Account account, String authority, Bundle extras) {
ContentResolver.validateSyncExtrasBundle(extras);
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
@@ -259,7 +322,7 @@ public final class ContentService extends IContentService.Stub {
* @param authority filter the pending and active syncs to cancel using this authority
*/
public void cancelSync(Account account, String authority) {
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
@@ -294,7 +357,7 @@ public final class ContentService extends IContentService.Stub {
public boolean getSyncAutomatically(Account account, String providerName) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -312,7 +375,7 @@ public final class ContentService extends IContentService.Stub {
public void setSyncAutomatically(Account account, String providerName, boolean sync) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -330,7 +393,7 @@ public final class ContentService extends IContentService.Stub {
long pollFrequency) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -344,7 +407,7 @@ public final class ContentService extends IContentService.Stub {
public void removePeriodicSync(Account account, String authority, Bundle extras) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -358,7 +421,7 @@ public final class ContentService extends IContentService.Stub {
public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -372,7 +435,7 @@ public final class ContentService extends IContentService.Stub {
public int getIsSyncable(Account account, String providerName) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -390,7 +453,7 @@ public final class ContentService extends IContentService.Stub {
public void setIsSyncable(Account account, String providerName, int syncable) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -407,7 +470,7 @@ public final class ContentService extends IContentService.Stub {
public boolean getMasterSyncAutomatically() {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -424,7 +487,7 @@ public final class ContentService extends IContentService.Stub {
public void setMasterSyncAutomatically(boolean flag) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -440,7 +503,7 @@ public final class ContentService extends IContentService.Stub {
public boolean isSyncActive(Account account, String authority) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -458,7 +521,7 @@ public final class ContentService extends IContentService.Stub {
public List<SyncInfo> getCurrentSyncs() {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -471,7 +534,7 @@ public final class ContentService extends IContentService.Stub {
public SyncStatusInfo getSyncStatus(Account account, String authority) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -489,7 +552,7 @@ public final class ContentService extends IContentService.Stub {
public boolean isSyncPending(Account account, String authority) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
- int userId = UserId.getCallingUserId();
+ int userId = UserHandle.getCallingUserId();
long identityToken = clearCallingIdentity();
try {
@@ -543,16 +606,18 @@ public final class ContentService extends IContentService.Stub {
public final IContentObserver observer;
public final int uid;
public final int pid;
- public final boolean notifyForDescendents;
+ public final boolean notifyForDescendants;
+ private final int userHandle;
private final Object observersLock;
public ObserverEntry(IContentObserver o, boolean n, Object observersLock,
- int _uid, int _pid) {
+ int _uid, int _pid, int _userHandle) {
this.observersLock = observersLock;
observer = o;
uid = _uid;
pid = _pid;
- notifyForDescendents = n;
+ userHandle = _userHandle;
+ notifyForDescendants = n;
try {
observer.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -571,7 +636,8 @@ public final class ContentService extends IContentService.Stub {
pidCounts.put(pid, pidCounts.get(pid)+1);
pw.print(prefix); pw.print(name); pw.print(": pid=");
pw.print(pid); pw.print(" uid=");
- pw.print(uid); pw.print(" target=");
+ pw.print(uid); pw.print(" user=");
+ pw.print(userHandle); pw.print(" target=");
pw.println(Integer.toHexString(System.identityHashCode(
observer != null ? observer.asBinder() : null)));
}
@@ -639,17 +705,21 @@ public final class ContentService extends IContentService.Stub {
return uri.getPathSegments().size() + 1;
}
+ // Invariant: userHandle is either a hard user number or is USER_ALL
public void addObserverLocked(Uri uri, IContentObserver observer,
- boolean notifyForDescendents, Object observersLock, int uid, int pid) {
- addObserverLocked(uri, 0, observer, notifyForDescendents, observersLock, uid, pid);
+ boolean notifyForDescendants, Object observersLock,
+ int uid, int pid, int userHandle) {
+ addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock,
+ uid, pid, userHandle);
}
private void addObserverLocked(Uri uri, int index, IContentObserver observer,
- boolean notifyForDescendents, Object observersLock, int uid, int pid) {
+ boolean notifyForDescendants, Object observersLock,
+ int uid, int pid, int userHandle) {
// If this is the leaf node add the observer
if (index == countUriSegments(uri)) {
- mObservers.add(new ObserverEntry(observer, notifyForDescendents, observersLock,
- uid, pid));
+ mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
+ uid, pid, userHandle));
return;
}
@@ -662,8 +732,8 @@ public final class ContentService extends IContentService.Stub {
for (int i = 0; i < N; i++) {
ObserverNode node = mChildren.get(i);
if (node.mName.equals(segment)) {
- node.addObserverLocked(uri, index + 1, observer, notifyForDescendents,
- observersLock, uid, pid);
+ node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
+ observersLock, uid, pid, userHandle);
return;
}
}
@@ -671,8 +741,8 @@ public final class ContentService extends IContentService.Stub {
// No child found, create one
ObserverNode node = new ObserverNode(segment);
mChildren.add(node);
- node.addObserverLocked(uri, index + 1, observer, notifyForDescendents,
- observersLock, uid, pid);
+ node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
+ observersLock, uid, pid, userHandle);
}
public boolean removeObserverLocked(IContentObserver observer) {
@@ -705,37 +775,49 @@ public final class ContentService extends IContentService.Stub {
}
private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
- boolean observerWantsSelfNotifications, ArrayList<ObserverCall> calls) {
+ boolean observerWantsSelfNotifications, int targetUserHandle,
+ ArrayList<ObserverCall> calls) {
int N = mObservers.size();
IBinder observerBinder = observer == null ? null : observer.asBinder();
for (int i = 0; i < N; i++) {
ObserverEntry entry = mObservers.get(i);
- // Don't notify the observer if it sent the notification and isn't interesed
+ // Don't notify the observer if it sent the notification and isn't interested
// in self notifications
boolean selfChange = (entry.observer.asBinder() == observerBinder);
if (selfChange && !observerWantsSelfNotifications) {
continue;
}
- // Make sure the observer is interested in the notification
- if (leaf || (!leaf && entry.notifyForDescendents)) {
- calls.add(new ObserverCall(this, entry.observer, selfChange));
+ // Does this observer match the target user?
+ if (targetUserHandle == UserHandle.USER_ALL
+ || entry.userHandle == UserHandle.USER_ALL
+ || targetUserHandle == entry.userHandle) {
+ // Make sure the observer is interested in the notification
+ if (leaf || (!leaf && entry.notifyForDescendants)) {
+ calls.add(new ObserverCall(this, entry.observer, selfChange));
+ }
}
}
}
+ /**
+ * targetUserHandle is either a hard user handle or is USER_ALL
+ */
public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
- boolean observerWantsSelfNotifications, ArrayList<ObserverCall> calls) {
+ boolean observerWantsSelfNotifications, int targetUserHandle,
+ ArrayList<ObserverCall> calls) {
String segment = null;
int segmentCount = countUriSegments(uri);
if (index >= segmentCount) {
// This is the leaf node, notify all observers
- collectMyObserversLocked(true, observer, observerWantsSelfNotifications, calls);
+ collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
+ targetUserHandle, calls);
} else if (index < segmentCount){
segment = getUriSegment(uri, index);
- // Notify any observers at this level who are interested in descendents
- collectMyObserversLocked(false, observer, observerWantsSelfNotifications, calls);
+ // Notify any observers at this level who are interested in descendants
+ collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
+ targetUserHandle, calls);
}
int N = mChildren.size();
@@ -744,7 +826,7 @@ public final class ContentService extends IContentService.Stub {
if (segment == null || node.mName.equals(segment)) {
// We found the child,
node.collectObserversLocked(uri, index + 1,
- observer, observerWantsSelfNotifications, calls);
+ observer, observerWantsSelfNotifications, targetUserHandle, calls);
if (segment != null) {
break;
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ffff9be..201b43f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -19,6 +19,7 @@ package android.content;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.DatabaseErrorHandler;
@@ -31,7 +32,11 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.UserHandle;
import android.util.AttributeSet;
+import android.view.CompatibilityInfoHolder;
+import android.view.Display;
+import android.view.WindowManager;
import java.io.File;
import java.io.FileInputStream;
@@ -58,18 +63,34 @@ public abstract class Context {
*/
public static final int MODE_PRIVATE = 0x0000;
/**
+ * @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.
* File creation mode: allow all other applications to have read access
* to the created file.
* @see #MODE_PRIVATE
* @see #MODE_WORLD_WRITEABLE
*/
+ @Deprecated
public static final int MODE_WORLD_READABLE = 0x0001;
/**
+ * @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.
* File creation mode: allow all other applications to have write access
* to the created file.
* @see #MODE_PRIVATE
* @see #MODE_WORLD_READABLE
*/
+ @Deprecated
public static final int MODE_WORLD_WRITEABLE = 0x0002;
/**
* File creation mode: for use with {@link #openFileOutput}, if the file
@@ -157,7 +178,7 @@ public abstract class Context {
* Flag for {@link #bindService}: indicates that the client application
* binding to this service considers the service to be more important than
* the app itself. When set, the platform will try to have the out of
- * memory kill the app before it kills the service it is bound to, though
+ * memory killer kill the app before it kills the service it is bound to, though
* this is not guaranteed to be the case.
*/
public static final int BIND_ABOVE_CLIENT = 0x0008;
@@ -198,6 +219,19 @@ public abstract class Context {
public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080;
/**
+ * @hide An idea that is not yet implemented.
+ * Flag for {@link #bindService}: If binding from an activity, consider
+ * this service to be visible like the binding activity is. That is,
+ * it will be treated as something more important to keep around than
+ * invisible background activities. This will impact the number of
+ * recent activities the user can switch between without having them
+ * restart. There is no guarantee this will be respected, as the system
+ * tries to balance such requests from one app vs. the importantance of
+ * keeping other apps around.
+ */
+ public static final int BIND_VISIBLE = 0x0100;
+
+ /**
* Flag for {@link #bindService}: Don't consider the bound service to be
* visible, even if the caller is visible.
* @hide
@@ -640,8 +674,12 @@ public abstract class Context {
* are some important differences:
*
* <ul>
- * <li>The platform does not monitor the space available in external storage,
- * and thus will not automatically delete these files. Note that you should
+ * <li>The platform does not always monitor the space available in external
+ * storage, and thus may not automatically delete these files. Currently
+ * the only time files here will be deleted by the platform is when running
+ * on {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} or later and
+ * {@link android.os.Environment#isExternalStorageEmulated()
+ * Environment.isExternalStorageEmulated()} returns true. Note that you should
* be managing the maximum space you will use for these anyway, just like
* with {@link #getCacheDir()}.
* <li>External files are not always available: they will disappear if the
@@ -851,6 +889,20 @@ public abstract class Context {
public abstract void startActivity(Intent intent);
/**
+ * Version of {@link #startActivity(Intent)} that allows you to specify the
+ * user the activity will be started for. This is not available to applications
+ * that are not pre-installed on the system image. Using it requires holding
+ * 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
+ * @hide
+ */
+ public void startActivityAsUser(Intent intent, UserHandle user) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Launch a new activity. You will not receive any information about when
* the activity exits.
*
@@ -878,6 +930,24 @@ public abstract class Context {
public abstract void startActivity(Intent intent, Bundle options);
/**
+ * Version of {@link #startActivity(Intent, Bundle)} that allows you to specify the
+ * user the activity will be started for. This is not available to applications
+ * that are not pre-installed on the system image. Using it requires holding
+ * the INTERACT_ACROSS_USERS_FULL permission.
+ * @param intent The description of the activity to start.
+ * @param options Additional options for how the Activity should be started.
+ * 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
+ * @hide
+ */
+ public void startActivityAsUser(Intent intent, Bundle options, UserHandle userId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Same as {@link #startActivities(Intent[], Bundle)} with no options
* specified.
*
@@ -917,6 +987,36 @@ public abstract class Context {
public abstract void startActivities(Intent[] intents, Bundle options);
/**
+ * @hide
+ * Launch multiple new activities. This is generally the same as calling
+ * {@link #startActivity(Intent)} for the first Intent in the array,
+ * that activity during its creation calling {@link #startActivity(Intent)}
+ * for the second entry, etc. Note that unlike that approach, generally
+ * none of the activities except the last in the array will be created
+ * at this point, but rather will be created when the user first visits
+ * them (due to pressing back from the activity on top).
+ *
+ * <p>This method throws {@link ActivityNotFoundException}
+ * if there was no Activity found for <em>any</em> given Intent. In this
+ * case the state of the activity stack is undefined (some Intents in the
+ * list may be on it, some not), so you probably want to avoid such situations.
+ *
+ * @param intents An array of Intents to be started.
+ * @param options Additional options for how the Activity should be started.
+ * @param userHandle The user for whom to launch the activities
+ * See {@link android.content.Context#startActivity(Intent, Bundle)
+ * Context.startActivity(Intent, Bundle)} for more details.
+ *
+ * @throws ActivityNotFoundException
+ *
+ * @see {@link #startActivities(Intent[])}
+ * @see PackageManager#resolveActivity
+ */
+ public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Same as {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)}
* with no options specified.
*
@@ -988,16 +1088,6 @@ public abstract class Context {
public abstract void sendBroadcast(Intent intent);
/**
- * Same as #sendBroadcast(Intent intent), but for a specific user. Used by the system only.
- * @param intent the intent to broadcast
- * @param userId user to send the intent to
- * @hide
- */
- public void sendBroadcast(Intent intent, int userId) {
- throw new RuntimeException("Not implemented. Must override in a subclass.");
- }
-
- /**
* Broadcast the given intent to all interested BroadcastReceivers, allowing
* an optional required permission to be enforced. This
* call is asynchronous; it returns immediately, and you will continue
@@ -1095,6 +1185,69 @@ public abstract class Context {
Bundle initialExtras);
/**
+ * Version of {@link #sendBroadcast(Intent)} that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image. Using it requires holding
+ * the INTERACT_ACROSS_USERS permission.
+ * @param intent The intent to broadcast
+ * @param user UserHandle to send the intent to.
+ * @see #sendBroadcast(Intent)
+ */
+ public abstract void sendBroadcastAsUser(Intent intent, UserHandle user);
+
+ /**
+ * Version of {@link #sendBroadcast(Intent, String)} that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image. Using it requires holding
+ * the INTERACT_ACROSS_USERS permission.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param user UserHandle to send the intent to.
+ * @param receiverPermission (optional) String naming a permission that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ *
+ * @see #sendBroadcast(Intent, String)
+ */
+ public abstract void sendBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission);
+
+ /**
+ * Version of
+ * {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)}
+ * that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image. Using it requires holding
+ * the INTERACT_ACROSS_USERS permission.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param user UserHandle to send the intent to.
+ * @param receiverPermission String naming a permissions that
+ * a receiver must hold in order to receive your broadcast.
+ * If null, no permission is required.
+ * @param resultReceiver Your own BroadcastReceiver to treat as the final
+ * receiver of the broadcast.
+ * @param scheduler A custom Handler with which to schedule the
+ * resultReceiver callback; if null it will be
+ * scheduled in the Context's main thread.
+ * @param initialCode An initial value for the result code. Often
+ * Activity.RESULT_OK.
+ * @param initialData An initial value for the result data. Often
+ * null.
+ * @param initialExtras An initial value for the result extras. Often
+ * null.
+ *
+ * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ public abstract void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras);
+
+ /**
* Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
* Intent you are sending stays around after the broadcast is complete,
* so that others can quickly retrieve that data through the return
@@ -1160,7 +1313,6 @@ public abstract class Context {
Handler scheduler, int initialCode, String initialData,
Bundle initialExtras);
-
/**
* Remove the data previously sent with {@link #sendStickyBroadcast},
* so that it is as if the sticky broadcast had never happened.
@@ -1176,6 +1328,70 @@ public abstract class Context {
public abstract void removeStickyBroadcast(Intent intent);
/**
+ * Version of {@link #sendStickyBroadcast(Intent)} that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image. Using it requires holding
+ * the INTERACT_ACROSS_USERS permission.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast, and the Intent will be held to
+ * be re-broadcast to future receivers.
+ * @param user UserHandle to send the intent to.
+ *
+ * @see #sendBroadcast(Intent)
+ */
+ public abstract void sendStickyBroadcastAsUser(Intent intent, UserHandle user);
+
+ /**
+ * Version of
+ * {@link #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)}
+ * that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image. Using it requires holding
+ * the INTERACT_ACROSS_USERS permission.
+ *
+ * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts.
+ *
+ * @param intent The Intent to broadcast; all receivers matching this
+ * Intent will receive the broadcast.
+ * @param user UserHandle to send the intent to.
+ * @param resultReceiver Your own BroadcastReceiver to treat as the final
+ * receiver of the broadcast.
+ * @param scheduler A custom Handler with which to schedule the
+ * resultReceiver callback; if null it will be
+ * scheduled in the Context's main thread.
+ * @param initialCode An initial value for the result code. Often
+ * Activity.RESULT_OK.
+ * @param initialData An initial value for the result data. Often
+ * null.
+ * @param initialExtras An initial value for the result extras. Often
+ * null.
+ *
+ * @see #sendStickyOrderedBroadcast(Intent, BroadcastReceiver, Handler, int, String, Bundle)
+ */
+ public abstract void sendStickyOrderedBroadcastAsUser(Intent intent,
+ UserHandle user, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras);
+
+ /**
+ * Version of {@link #removeStickyBroadcast(Intent)} that allows you to specify the
+ * user the broadcast will be sent to. This is not available to applications
+ * that are not pre-installed on the system image. Using it requires holding
+ * the INTERACT_ACROSS_USERS permission.
+ *
+ * <p>You must hold the {@link android.Manifest.permission#BROADCAST_STICKY}
+ * permission in order to use this API. If you do not hold that
+ * permission, {@link SecurityException} will be thrown.
+ *
+ * @param intent The Intent that was previously broadcast.
+ * @param user UserHandle to remove the sticky broadcast from.
+ *
+ * @see #sendStickyBroadcastAsUser
+ */
+ public abstract void removeStickyBroadcastAsUser(Intent intent, UserHandle user);
+
+ /**
* Register a BroadcastReceiver to be run in the main activity thread. The
* <var>receiver</var> will be called with any broadcast Intent that
* matches <var>filter</var>, in the main application thread.
@@ -1258,9 +1474,35 @@ public abstract class Context {
* @see #unregisterReceiver
*/
public abstract Intent registerReceiver(BroadcastReceiver receiver,
- IntentFilter filter,
- String broadcastPermission,
- Handler scheduler);
+ IntentFilter filter, String broadcastPermission, Handler scheduler);
+
+ /**
+ * @hide
+ * Same as {@link #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)
+ * but for a specific user. This receiver will receiver broadcasts that
+ * are sent to the requested user. It
+ * requires holding the {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
+ * permission.
+ *
+ * @param receiver The BroadcastReceiver to handle the broadcast.
+ * @param user UserHandle to send the intent to.
+ * @param filter Selects the Intent broadcasts to be received.
+ * @param broadcastPermission String naming a permissions that a
+ * broadcaster must hold in order to send an Intent to you. If null,
+ * no permission is required.
+ * @param scheduler Handler identifying the thread that will receive
+ * the Intent. If null, the main thread of the process will be used.
+ *
+ * @return The first sticky intent found that matches <var>filter</var>,
+ * or null if there are none.
+ *
+ * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler
+ * @see #sendBroadcast
+ * @see #unregisterReceiver
+ */
+ public abstract Intent registerReceiverAsUser(BroadcastReceiver receiver,
+ UserHandle user, IntentFilter filter, String broadcastPermission,
+ Handler scheduler);
/**
* Unregister a previously registered BroadcastReceiver. <em>All</em>
@@ -1351,6 +1593,16 @@ public abstract class Context {
public abstract boolean stopService(Intent service);
/**
+ * @hide like {@link #startService(Intent)} but for a specific user.
+ */
+ public abstract ComponentName startServiceAsUser(Intent service, UserHandle user);
+
+ /**
+ * @hide like {@link #stopService(Intent)} but for a specific user.
+ */
+ public abstract boolean stopServiceAsUser(Intent service, UserHandle user);
+
+ /**
* Connect to an application service, creating it if needed. This defines
* a dependency between your application and the service. The given
* <var>conn</var> will receive the service object when it is created and be
@@ -1379,6 +1631,7 @@ public abstract class Context {
* description (action, category, etc) to match an
* {@link IntentFilter} published by a service.
* @param conn Receives information as the service is started and stopped.
+ * This must be a valid ServiceConnection object; it must not be null.
* @param flags Operation options for the binding. May be 0,
* {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
* {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
@@ -1400,11 +1653,11 @@ public abstract class Context {
int flags);
/**
- * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userId
+ * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
* argument for use by system server and other multi-user aware code.
* @hide
*/
- public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) {
+ public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
@@ -1414,7 +1667,7 @@ public abstract class Context {
* stop at any time.
*
* @param conn The connection interface previously supplied to
- * bindService().
+ * bindService(). This parameter must not be null.
*
* @see #bindService
*/
@@ -1906,6 +2159,15 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.bluetooth.BluetoothAdapter} for using Bluetooth.
+ *
+ * @see #getSystemService
+ * @hide
+ */
+ public static final String BLUETOOTH_SERVICE = "bluetooth";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.net.sip.SipManager} for accessing the SIP related service.
*
* @see #getSystemService
@@ -1945,6 +2207,15 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.hardware.display.DisplayManager} for interacting with display devices.
+ *
+ * @see #getSystemService
+ * @see android.hardware.display.DisplayManager
+ */
+ public static final String DISPLAY_SERVICE = "display";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.os.SchedulingPolicyService} for managing scheduling policy.
*
* @see #getSystemService
@@ -1955,6 +2226,15 @@ public abstract class Context {
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.
+ *
+ * @see #getSystemService
+ * @see android.os.UserManager
+ */
+ public static final String USER_SERVICE = "user";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
@@ -2357,6 +2637,65 @@ public abstract class Context {
int flags) throws PackageManager.NameNotFoundException;
/**
+ * Similar to {@link #createPackageContext(String, int)}, but with a
+ * different {@link UserHandle}. For example, {@link #getContentResolver()}
+ * will open any {@link Uri} as the given user.
+ *
+ * @hide
+ */
+ public abstract Context createPackageContextAsUser(
+ String packageName, int flags, UserHandle user)
+ throws PackageManager.NameNotFoundException;
+
+ /**
+ * Return a new Context object for the current Context but whose resources
+ * are adjusted to match the given Configuration. Each call to this method
+ * returns a new instance of a Context object; Context objects are not
+ * shared, however common state (ClassLoader, other Resources for the
+ * same configuration) may be so the Context itself can be fairly lightweight.
+ *
+ * @param overrideConfiguration A {@link Configuration} specifying what
+ * values to modify in the base Configuration of the original Context's
+ * resources. If the base configuration changes (such as due to an
+ * 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.
+ */
+ public abstract Context createConfigurationContext(Configuration overrideConfiguration);
+
+ /**
+ * Return a new Context object for the current Context but whose resources
+ * are adjusted to match the metrics of the given Display. Each call to this method
+ * returns a new instance of a Context object; Context objects are not
+ * shared, however common state (ClassLoader, other Resources for the
+ * same configuration) may be so the Context itself can be fairly lightweight.
+ *
+ * The returned display Context provides a {@link WindowManager}
+ * (see {@link #getSystemService(String)}) that is configured to show windows
+ * on the given display. The WindowManager's {@link WindowManager#getDefaultDisplay}
+ * method can be used to retrieve the Display from the returned Context.
+ *
+ * @param display A {@link Display} object specifying the display
+ * for whose metrics the Context's resources should be tailored and upon which
+ * new windows should be shown.
+ *
+ * @return A 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.
+ *
+ * @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);
+
+ /**
* Indicates whether this Context is restricted.
*
* @return True if this Context is restricted, false otherwise.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 6b950e0..84ad667 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -16,9 +16,13 @@
package android.content;
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.LoadedApk;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
@@ -29,6 +33,10 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.view.CompatibilityInfoHolder;
+import android.view.Display;
import java.io.File;
import java.io.FileInputStream;
@@ -276,11 +284,23 @@ public class ContextWrapper extends Context {
mBase.startActivity(intent);
}
+ /** @hide */
+ @Override
+ public void startActivityAsUser(Intent intent, UserHandle user) {
+ mBase.startActivityAsUser(intent, user);
+ }
+
@Override
public void startActivity(Intent intent, Bundle options) {
mBase.startActivity(intent, options);
}
+ /** @hide */
+ @Override
+ public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+ mBase.startActivityAsUser(intent, options, user);
+ }
+
@Override
public void startActivities(Intent[] intents) {
mBase.startActivities(intents);
@@ -291,6 +311,12 @@ public class ContextWrapper extends Context {
mBase.startActivities(intents, options);
}
+ /** @hide */
+ @Override
+ public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+ mBase.startActivitiesAsUser(intents, options, userHandle);
+ }
+
@Override
public void startIntentSender(IntentSender intent,
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
@@ -312,12 +338,6 @@ public class ContextWrapper extends Context {
mBase.sendBroadcast(intent);
}
- /** @hide */
- @Override
- public void sendBroadcast(Intent intent, int userId) {
- mBase.sendBroadcast(intent, userId);
- }
-
@Override
public void sendBroadcast(Intent intent, String receiverPermission) {
mBase.sendBroadcast(intent, receiverPermission);
@@ -340,6 +360,25 @@ public class ContextWrapper extends Context {
}
@Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+ mBase.sendBroadcastAsUser(intent, user);
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission) {
+ mBase.sendBroadcastAsUser(intent, user, receiverPermission);
+ }
+
+ @Override
+ public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
+ String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
+ int initialCode, String initialData, Bundle initialExtras) {
+ mBase.sendOrderedBroadcastAsUser(intent, user, receiverPermission, resultReceiver,
+ scheduler, initialCode, initialData, initialExtras);
+ }
+
+ @Override
public void sendStickyBroadcast(Intent intent) {
mBase.sendStickyBroadcast(intent);
}
@@ -360,6 +399,25 @@ public class ContextWrapper extends Context {
}
@Override
+ public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
+ mBase.sendStickyBroadcastAsUser(intent, user);
+ }
+
+ @Override
+ public void sendStickyOrderedBroadcastAsUser(Intent intent,
+ UserHandle user, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
+ mBase.sendStickyOrderedBroadcastAsUser(intent, user, resultReceiver,
+ scheduler, initialCode, initialData, initialExtras);
+ }
+
+ @Override
+ public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) {
+ mBase.removeStickyBroadcastAsUser(intent, user);
+ }
+
+ @Override
public Intent registerReceiver(
BroadcastReceiver receiver, IntentFilter filter) {
return mBase.registerReceiver(receiver, filter);
@@ -373,6 +431,15 @@ public class ContextWrapper extends Context {
scheduler);
}
+ /** @hide */
+ @Override
+ public Intent registerReceiverAsUser(
+ BroadcastReceiver receiver, UserHandle user, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ return mBase.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+ scheduler);
+ }
+
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
mBase.unregisterReceiver(receiver);
@@ -388,6 +455,18 @@ public class ContextWrapper extends Context {
return mBase.stopService(name);
}
+ /** @hide */
+ @Override
+ public ComponentName startServiceAsUser(Intent service, UserHandle user) {
+ return mBase.startServiceAsUser(service, user);
+ }
+
+ /** @hide */
+ @Override
+ public boolean stopServiceAsUser(Intent name, UserHandle user) {
+ return mBase.stopServiceAsUser(name, user);
+ }
+
@Override
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
@@ -396,8 +475,8 @@ public class ContextWrapper extends Context {
/** @hide */
@Override
- public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) {
- return mBase.bindService(service, conn, flags, userId);
+ public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) {
+ return mBase.bindService(service, conn, flags, userHandle);
}
@Override
@@ -513,8 +592,31 @@ public class ContextWrapper extends Context {
return mBase.createPackageContext(packageName, flags);
}
+ /** @hide */
+ @Override
+ public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
+ throws PackageManager.NameNotFoundException {
+ return mBase.createPackageContextAsUser(packageName, flags, user);
+ }
+
+ @Override
+ public Context createConfigurationContext(Configuration overrideConfiguration) {
+ return mBase.createConfigurationContext(overrideConfiguration);
+ }
+
+ @Override
+ public Context createDisplayContext(Display display) {
+ return mBase.createDisplayContext(display);
+ }
+
@Override
public boolean isRestricted() {
return mBase.isRestricted();
}
+
+ /** @hide */
+ @Override
+ public CompatibilityInfoHolder getCompatibilityInfo(int displayId) {
+ return mBase.getCompatibilityInfo(displayId);
+ }
}
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index 86a9392..f956bcf 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -30,12 +30,28 @@ import android.database.IContentObserver;
* @hide
*/
interface IContentService {
- void registerContentObserver(in Uri uri, boolean notifyForDescendentsn,
- IContentObserver observer);
void unregisterContentObserver(IContentObserver observer);
+ /**
+ * Register a content observer tied to a specific user's view of the provider.
+ * @param userHandle the user whose view of the provider is to be observed. May be
+ * the calling user without requiring any permission, otherwise the caller needs to
+ * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and
+ * USER_CURRENT are properly handled.
+ */
+ void registerContentObserver(in Uri uri, boolean notifyForDescendants,
+ IContentObserver observer, int userHandle);
+
+ /**
+ * Notify observers of a particular user's view of the provider.
+ * @param userHandle the user whose view of the provider is to be notified. May be
+ * the calling user without requiring any permission, otherwise the caller needs to
+ * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL
+ * USER_CURRENT are properly interpreted.
+ */
void notifyChange(in Uri uri, IContentObserver observer,
- boolean observerWantsSelfNotifications, boolean syncToNetwork);
+ boolean observerWantsSelfNotifications, boolean syncToNetwork,
+ int userHandle);
void requestSync(in Account account, String authority, in Bundle extras);
void cancelSync(in Account account, String authority);
diff --git a/core/java/android/content/IIntentReceiver.aidl b/core/java/android/content/IIntentReceiver.aidl
index 6f2f7c4..3d92723 100755
--- a/core/java/android/content/IIntentReceiver.aidl
+++ b/core/java/android/content/IIntentReceiver.aidl
@@ -27,7 +27,7 @@ import android.os.Bundle;
* {@hide}
*/
oneway interface IIntentReceiver {
- void performReceive(in Intent intent, int resultCode,
- String data, in Bundle extras, boolean ordered, boolean sticky);
+ void performReceive(in Intent intent, int resultCode, String data,
+ in Bundle extras, boolean ordered, boolean sticky, int sendingUser);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3fdf451..c14a703 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -27,7 +27,6 @@ import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
-import android.media.RemoteControlClient;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
@@ -571,7 +570,9 @@ import java.util.Set;
* <li> {@link #EXTRA_INITIAL_INTENTS}
* <li> {@link #EXTRA_INTENT}
* <li> {@link #EXTRA_KEY_EVENT}
+ * <li> {@link #EXTRA_ORIGINATING_URI}
* <li> {@link #EXTRA_PHONE_NUMBER}
+ * <li> {@link #EXTRA_REFERRER}
* <li> {@link #EXTRA_REMOTE_INTENT_TOKEN}
* <li> {@link #EXTRA_REPLACING}
* <li> {@link #EXTRA_SHORTCUT_ICON}
@@ -1253,7 +1254,9 @@ public class Intent implements Parcelable, Cloneable {
* Activity Action: Launch application installer.
* <p>
* Input: The data must be a content: or file: URI at which the application
- * can be retrieved. You can optionally supply
+ * can be retrieved. As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1},
+ * you can also use "package:<package-name>" to install an application for the
+ * current user that is already installed for another user. You can optionally supply
* {@link #EXTRA_INSTALLER_PACKAGE_NAME}, {@link #EXTRA_NOT_UNKNOWN_SOURCE},
* {@link #EXTRA_ALLOW_REPLACE}, and {@link #EXTRA_RETURN_RESULT}.
* <p>
@@ -1286,6 +1289,30 @@ public class Intent implements Parcelable, Cloneable {
= "android.intent.extra.NOT_UNKNOWN_SOURCE";
/**
+ * Used as a URI extra field with {@link #ACTION_INSTALL_PACKAGE} and
+ * {@link #ACTION_VIEW} to indicate the URI from which the local APK in the Intent
+ * data field originated from.
+ */
+ public static final String EXTRA_ORIGINATING_URI
+ = "android.intent.extra.ORIGINATING_URI";
+
+ /**
+ * Used as a URI extra field with {@link #ACTION_INSTALL_PACKAGE} and
+ * {@link #ACTION_VIEW} to indicate the HTTP referrer URI associated with the Intent
+ * data field or {@link #EXTRA_ORIGINATING_URI}.
+ */
+ public static final String EXTRA_REFERRER
+ = "android.intent.extra.REFERRER";
+
+ /**
+ * Used as an int extra field with {@link #ACTION_INSTALL_PACKAGE} and
+ * {@link} #ACTION_VIEW} to indicate the uid of the package that initiated the install
+ * @hide
+ */
+ public static final String EXTRA_ORIGINATING_UID
+ = "android.intent.extra.ORIGINATING_UID";
+
+ /**
* Used as a boolean extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
* package. Tells the installer UI to skip the confirmation with the user
* if the .apk is replacing an existing one.
@@ -1328,6 +1355,13 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_UNINSTALL_PACKAGE = "android.intent.action.UNINSTALL_PACKAGE";
/**
+ * Specify whether the package should be uninstalled for all users.
+ * @hide because these should not be part of normal application flow.
+ */
+ public static final String EXTRA_UNINSTALL_ALL_USERS
+ = "android.intent.extra.UNINSTALL_ALL_USERS";
+
+ /**
* A string associated with a {@link #ACTION_UPGRADE_SETUP} activity
* describing the last run version of the platform that was setup.
* @hide
@@ -1356,6 +1390,24 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON";
/**
+ * Broadcast Action: Sent after the system stops dreaming.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ * It is only sent to registered receivers.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DREAMING_STOPPED = "android.intent.action.DREAMING_STOPPED";
+
+ /**
+ * Broadcast Action: Sent after the system starts dreaming.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.
+ * It is only sent to registered receivers.</p>
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DREAMING_STARTED = "android.intent.action.DREAMING_STARTED";
+
+ /**
* Broadcast Action: Sent when the user is present after device wakes up (e.g when the
* keyguard is gone).
*
@@ -1456,7 +1508,7 @@ public class Intent implements Parcelable, Cloneable {
* Broadcast Action: A new application package has been installed on the
* device. The data contains the name of the package. Note that the
* newly installed package does <em>not</em> receive this broadcast.
- * <p>My include the following extras:
+ * <p>May include the following extras:
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package.
* <li> {@link #EXTRA_REPLACING} is set to true if this is following
@@ -1472,7 +1524,7 @@ public class Intent implements Parcelable, Cloneable {
* Broadcast Action: A new version of an application package has been
* installed, replacing an existing version that was previously installed.
* The data contains the name of the package.
- * <p>My include the following extras:
+ * <p>May include the following extras:
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package.
* </ul>
@@ -1624,6 +1676,15 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_PACKAGE_NEEDS_VERIFICATION = "android.intent.action.PACKAGE_NEEDS_VERIFICATION";
/**
+ * Broadcast Action: Sent to the system package verifier when a package is
+ * verified. The data contains the package URI.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
+
+ /**
* Broadcast Action: Resources for a set of packages (which were
* previously unavailable) are currently
* available since the media on which they exist is available.
@@ -2261,29 +2322,104 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.PRE_BOOT_COMPLETED";
/**
- * Broadcast sent to the system when a user is added. Carries an extra EXTRA_USERID that has the
- * userid of the new user.
+ * Sent the first time a user is starting, to allow system apps to
+ * perform one time initialization. (This will not be seen by third
+ * party applications because a newly initialized user does not have any
+ * third party applications installed for it.) This is sent early in
+ * starting the user, around the time the home app is started, before
+ * {@link #ACTION_BOOT_COMPLETED} is sent.
+ */
+ public static final String ACTION_USER_INITIALIZE =
+ "android.intent.action.USER_INITIALIZE";
+
+ /**
+ * Sent when a user switch is happening, causing the process's user to be
+ * brought to the foreground. This is only sent to receivers registered
+ * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver}. It is sent to the user that is going to the
+ * foreground.
+ */
+ public static final String ACTION_USER_FOREGROUND =
+ "android.intent.action.USER_FOREGROUND";
+
+ /**
+ * Sent when a user switch is happening, causing the process's user to be
+ * sent to the background. This is only sent to receivers registered
+ * through {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)
+ * Context.registerReceiver}. It is sent to the user that is going to the
+ * background.
+ */
+ public static final String ACTION_USER_BACKGROUND =
+ "android.intent.action.USER_BACKGROUND";
+
+ /**
+ * Broadcast sent to the system when a user is added. Carries an extra EXTRA_USER_HANDLE that has the
+ * userHandle of the new user. It is sent to all running users. You must hold
+ * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
* @hide
*/
public static final String ACTION_USER_ADDED =
"android.intent.action.USER_ADDED";
/**
- * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USERID that has
- * the userid of the user.
+ * Broadcast sent to the system when a user is started. Carries an extra EXTRA_USER_HANDLE that has
+ * the userHandle of the user. This is only sent to
+ * registered receivers, not manifest receivers. It is sent to the user
+ * that has been started.
+ * @hide
+ */
+ public static final String ACTION_USER_STARTED =
+ "android.intent.action.USER_STARTED";
+
+ /**
+ * Broadcast sent to the system when a user is stopped. Carries an extra EXTRA_USER_HANDLE that has
+ * the userHandle of the user. This is similar to {@link #ACTION_PACKAGE_RESTARTED},
+ * but for an entire user instead of a specific package. This is only sent to
+ * registered receivers, not manifest receivers. It is sent to all running
+ * users <em>except</em> the one that has just been stopped (which is no
+ * longer running).
+ * @hide
+ */
+ public static final String ACTION_USER_STOPPED =
+ "android.intent.action.USER_STOPPED";
+
+ /**
+ * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USER_HANDLE that has
+ * the userHandle of the user. It is sent to all running users except the
+ * one that has been removed. You must hold
+ * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
* @hide
*/
public static final String ACTION_USER_REMOVED =
"android.intent.action.USER_REMOVED";
/**
- * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USERID that has
- * the userid of the user to become the current one.
+ * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USER_HANDLE that has
+ * the userHandle of the user to become the current one. This is only sent to
+ * registered receivers, not manifest receivers. It is sent to all running users.
+ * You must hold
+ * {@link android.Manifest.permission#MANAGE_USERS} to receive this broadcast.
* @hide
*/
public static final String ACTION_USER_SWITCHED =
"android.intent.action.USER_SWITCHED";
+ /**
+ * Broadcast sent to the system when a user's information changes. Carries an extra
+ * {@link #EXTRA_USER_HANDLE} to indicate which user's information changed.
+ * This is only sent to registered receivers, not manifest receivers. It is sent to the user
+ * whose information has changed.
+ * @hide
+ */
+ public static final String ACTION_USER_INFO_CHANGED =
+ "android.intent.action.USER_INFO_CHANGED";
+
+ /**
+ * Sent when the user taps on the clock widget in the system's "quick settings" area.
+ */
+ public static final String ACTION_QUICK_CLOCK =
+ "android.intent.action.QUICK_CLOCK";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -2693,6 +2829,15 @@ public class Intent implements Parcelable, Cloneable {
public static final String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED";
/**
+ * @hide
+ * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+ * intents to indicate that at this point the package has been removed for
+ * all users on the device.
+ */
+ public static final String EXTRA_REMOVED_FOR_ALL_USERS
+ = "android.intent.extra.REMOVED_FOR_ALL_USERS";
+
+ /**
* Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
* intents to indicate that this is a replacement of the package, so this
* broadcast will immediately be followed by an add broadcast for a
@@ -2834,12 +2979,12 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.extra.LOCAL_ONLY";
/**
- * The userid carried with broadcast intents related to addition, removal and switching of users
+ * The userHandle carried with broadcast intents related to addition, removal and switching of users
* - {@link #ACTION_USER_ADDED}, {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
* @hide
*/
- public static final String EXTRA_USERID =
- "android.intent.extra.user_id";
+ public static final String EXTRA_USER_HANDLE =
+ "android.intent.extra.user_handle";
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index 4db4bdc..166495b 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -27,6 +27,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.util.AndroidException;
@@ -113,8 +114,8 @@ public class IntentSender implements Parcelable {
mWho = who;
mHandler = handler;
}
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean serialized, boolean sticky) {
+ public void performReceive(Intent intent, int resultCode, String data,
+ Bundle extras, boolean serialized, boolean sticky, int sendingUser) {
mIntent = intent;
mResultCode = resultCode;
mResultData = data;
@@ -204,6 +205,20 @@ public class IntentSender implements Parcelable {
}
/**
+ * @deprecated Renamed to {@link #getCreatorPackage()}.
+ */
+ @Deprecated
+ public String getTargetPackage() {
+ try {
+ return ActivityManagerNative.getDefault()
+ .getPackageForIntentSender(mTarget);
+ } catch (RemoteException e) {
+ // Should never happen.
+ return null;
+ }
+ }
+
+ /**
* Return the package name of the application that created this
* IntentSender, that is the identity under which you will actually be
* sending the Intent. The returned string is supplied by the system, so
@@ -212,7 +227,7 @@ public class IntentSender implements Parcelable {
* @return The package name of the PendingIntent, or null if there is
* none associated with it.
*/
- public String getTargetPackage() {
+ public String getCreatorPackage() {
try {
return ActivityManagerNative.getDefault()
.getPackageForIntentSender(mTarget);
@@ -223,6 +238,47 @@ public class IntentSender implements Parcelable {
}
/**
+ * Return the uid of the application that created this
+ * PendingIntent, that is the identity under which you will actually be
+ * sending the Intent. The returned integer is supplied by the system, so
+ * that an application can not spoof its uid.
+ *
+ * @return The uid of the PendingIntent, or -1 if there is
+ * none associated with it.
+ */
+ public int getCreatorUid() {
+ try {
+ return ActivityManagerNative.getDefault()
+ .getUidForIntentSender(mTarget);
+ } catch (RemoteException e) {
+ // Should never happen.
+ return -1;
+ }
+ }
+
+ /**
+ * Return the user handle of the application that created this
+ * PendingIntent, that is the user under which you will actually be
+ * sending the Intent. The returned UserHandle is supplied by the system, so
+ * that an application can not spoof its user. See
+ * {@link android.os.Process#myUserHandle() Process.myUserHandle()} for
+ * more explanation of user handles.
+ *
+ * @return The user handle of the PendingIntent, or null if there is
+ * none associated with it.
+ */
+ public UserHandle getCreatorUserHandle() {
+ try {
+ int uid = ActivityManagerNative.getDefault()
+ .getUidForIntentSender(mTarget);
+ return uid > 0 ? new UserHandle(UserHandle.getUserId(uid)) : null;
+ } catch (RemoteException e) {
+ // Should never happen.
+ return null;
+ }
+ }
+
+ /**
* Comparison operator on two IntentSender objects, such that true
* is returned then they both represent the same operation from the
* same package.
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index badcb03..564a804 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -16,10 +16,6 @@
package android.content;
-import com.android.internal.R;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
import android.accounts.Account;
import android.accounts.AccountAndUser;
import android.accounts.AccountManager;
@@ -27,7 +23,6 @@ import android.accounts.AccountManagerService;
import android.accounts.OnAccountsUpdateListener;
import android.app.ActivityManager;
import android.app.AlarmManager;
-import android.app.AppGlobals;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -52,7 +47,8 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.os.UserId;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.os.WorkSource;
import android.provider.Settings;
import android.text.format.DateUtils;
@@ -61,6 +57,11 @@ import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
+import com.android.internal.R;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -74,6 +75,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
/**
@@ -150,7 +152,7 @@ public class SyncManager implements OnAccountsUpdateListener {
private AlarmManager mAlarmService = null;
private SyncStorageEngine mSyncStorageEngine;
- public SyncQueue mSyncQueue;
+ final public SyncQueue mSyncQueue;
protected final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList();
@@ -173,7 +175,7 @@ public class SyncManager implements OnAccountsUpdateListener {
Log.v(TAG, "Internal storage is low.");
}
mStorageIsLow = true;
- cancelActiveSync(null /* any account */, UserId.USER_ALL,
+ cancelActiveSync(null /* any account */, UserHandle.USER_ALL,
null /* any authority */);
} else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -194,28 +196,38 @@ public class SyncManager implements OnAccountsUpdateListener {
private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (getConnectivityManager().getBackgroundDataSetting()) {
- scheduleSync(null /* account */, UserId.USER_ALL, null /* authority */,
+ scheduleSync(null /* account */, UserHandle.USER_ALL, null /* authority */,
new Bundle(), 0 /* delay */,
false /* onlyThoseWithUnknownSyncableState */);
}
}
};
+ private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ onAccountsUpdated(null);
+ }
+ };
+
private final PowerManager mPowerManager;
// Use this as a random offset to seed all periodic syncs
private int mSyncRandomOffsetMillis;
+ private UserManager mUserManager;
+
private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
- private List<UserInfo> getAllUsers() {
- try {
- return AppGlobals.getPackageManager().getUsers();
- } catch (RemoteException re) {
- // Local to system process, shouldn't happen
+ private UserManager getUserManager() {
+ if (mUserManager == null) {
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
}
- return null;
+ return mUserManager;
+ }
+
+ private List<UserInfo> getAllUsers() {
+ return getUserManager().getUsers();
}
private boolean containsAccountAndUser(AccountAndUser[] accounts, Account account, int userId) {
@@ -282,7 +294,7 @@ public class SyncManager implements OnAccountsUpdateListener {
// a chance to set their syncable state.
boolean onlyThoseWithUnkownSyncableState = justBootedUp;
- scheduleSync(null, UserId.USER_ALL, null, null, 0 /* no delay */,
+ scheduleSync(null, UserHandle.USER_ALL, null, null, 0 /* no delay */,
onlyThoseWithUnkownSyncableState);
}
}
@@ -323,7 +335,21 @@ public class SyncManager implements OnAccountsUpdateListener {
private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- onUserRemoved(intent);
+ String action = intent.getAction();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ Log.i(TAG, "User removed - cleanup: u" + userId);
+ onUserRemoved(intent);
+ } else if (Intent.ACTION_USER_STARTED.equals(action)) {
+ Log.i(TAG, "User started - check alarms: u" + userId);
+ sendCheckAlarmsMessage();
+ } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+ Log.i(TAG, "User stopped - stop syncs: u" + userId);
+ cancelActiveSync(
+ null /* any account */,
+ userId,
+ null /* any authority */);
+ }
}
};
@@ -366,7 +392,7 @@ public class SyncManager implements OnAccountsUpdateListener {
mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() {
public void onServiceChanged(SyncAdapterType type, boolean removed) {
if (!removed) {
- scheduleSync(null, UserId.USER_ALL, type.authority, null, 0 /* no delay */,
+ scheduleSync(null, UserHandle.USER_ALL, type.authority, null, 0 /* no delay */,
false /* onlyThoseWithUnkownSyncableState */);
}
}
@@ -396,7 +422,9 @@ public class SyncManager implements OnAccountsUpdateListener {
intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiver(mUserIntentReceiver, intentFilter);
+ intentFilter.addAction(Intent.ACTION_USER_STARTED);
+ mContext.registerReceiverAsUser(
+ mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
if (!factoryTest) {
mNotificationMgr = (NotificationManager)
@@ -434,8 +462,11 @@ public class SyncManager implements OnAccountsUpdateListener {
});
if (!factoryTest) {
- AccountManager.get(mContext).addOnAccountsUpdatedListener(SyncManager.this,
- mSyncHandler, false /* updateImmediately */);
+ // Register for account list updates for all users
+ mContext.registerReceiverAsUser(mAccountsUpdatedReceiver,
+ UserHandle.ALL,
+ new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION),
+ null, null);
// do this synchronously to ensure we have the accounts before this call returns
onAccountsUpdated(null);
}
@@ -512,7 +543,7 @@ public class SyncManager implements OnAccountsUpdateListener {
}
AccountAndUser[] accounts;
- if (requestedAccount != null && userId != UserId.USER_ALL) {
+ if (requestedAccount != null && userId != UserHandle.USER_ALL) {
accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) };
} else {
// if the accounts aren't configured yet then we can't support an account-less
@@ -746,8 +777,8 @@ public class SyncManager implements OnAccountsUpdateListener {
}
// Cap the delay
- long maxSyncRetryTimeInSeconds = Settings.Secure.getLong(mContext.getContentResolver(),
- Settings.Secure.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
+ long maxSyncRetryTimeInSeconds = Settings.Global.getLong(mContext.getContentResolver(),
+ Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
@@ -890,9 +921,12 @@ public class SyncManager implements OnAccountsUpdateListener {
}
private void onUserRemoved(Intent intent) {
- int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1);
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userId == -1) return;
+ removeUser(userId);
+ }
+ private void removeUser(int userId) {
// Clean up the storage engine database
mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
onAccountsUpdated(null);
@@ -975,7 +1009,7 @@ public class SyncManager implements OnAccountsUpdateListener {
mSyncHandler.sendMessage(msg);
}
- boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info) {
+ boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info, int userId) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this);
}
@@ -984,8 +1018,9 @@ public class SyncManager implements OnAccountsUpdateListener {
intent.setComponent(info.componentName);
intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.sync_binding_label);
- intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
- mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0));
+ intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
+ mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0,
+ null, new UserHandle(userId)));
mBound = true;
final boolean bindResult = mContext.bindService(intent, this,
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
@@ -1261,7 +1296,8 @@ public class SyncManager implements OnAccountsUpdateListener {
final String accountKey;
if (authority != null) {
authorityName = authority.authority;
- accountKey = authority.account.name + "/" + authority.account.type;
+ accountKey = authority.account.name + "/" + authority.account.type
+ + " u" + authority.userId;
} else {
authorityName = "Unknown";
accountKey = "Unknown";
@@ -1388,7 +1424,8 @@ public class SyncManager implements OnAccountsUpdateListener {
final String accountKey;
if (authority != null) {
authorityName = authority.authority;
- accountKey = authority.account.name + "/" + authority.account.type;
+ accountKey = authority.account.name + "/" + authority.account.type
+ + " u" + authority.userId;
} else {
authorityName = "Unknown";
accountKey = "Unknown";
@@ -1918,6 +1955,10 @@ public class SyncManager implements OnAccountsUpdateListener {
}
Iterator<SyncOperation> operationIterator =
mSyncQueue.mOperationsMap.values().iterator();
+
+ final ActivityManager activityManager
+ = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ final Set<Integer> removedUsers = Sets.newHashSet();
while (operationIterator.hasNext()) {
final SyncOperation op = operationIterator.next();
@@ -1937,6 +1978,15 @@ public class SyncManager implements OnAccountsUpdateListener {
continue;
}
+ // if the user in not running, drop the request
+ if (!activityManager.isUserRunning(op.userId)) {
+ final UserInfo userInfo = mUserManager.getUserInfo(op.userId);
+ if (userInfo == null) {
+ removedUsers.add(op.userId);
+ }
+ continue;
+ }
+
// if the next run time is in the future, meaning there are no syncs ready
// to run, return the time
if (op.effectiveRunTime > now) {
@@ -1977,6 +2027,12 @@ public class SyncManager implements OnAccountsUpdateListener {
operations.add(op);
}
+ for (Integer user : removedUsers) {
+ // if it's still removed
+ if (mUserManager.getUserInfo(user) == null) {
+ removeUser(user);
+ }
+ }
}
// find the next operation to dispatch, if one is ready
@@ -2127,7 +2183,7 @@ public class SyncManager implements OnAccountsUpdateListener {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext);
}
- if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) {
+ if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo, op.userId)) {
Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
closeActiveSyncContext(activeSyncContext);
return false;
@@ -2162,20 +2218,20 @@ public class SyncManager implements OnAccountsUpdateListener {
new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
for (ActiveSyncContext activeSyncContext : activeSyncs) {
if (activeSyncContext != null) {
- // if an authority was specified then only cancel the sync if it matches
+ // if an account was specified then only cancel the sync if it matches
if (account != null) {
if (!account.equals(activeSyncContext.mSyncOperation.account)) {
continue;
}
}
- // if an account was specified then only cancel the sync if it matches
+ // if an authority was specified then only cancel the sync if it matches
if (authority != null) {
if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
continue;
}
}
// check if the userid matches
- if (userId != UserId.USER_ALL
+ if (userId != UserHandle.USER_ALL
&& userId != activeSyncContext.mSyncOperation.userId) {
continue;
}
@@ -2250,10 +2306,12 @@ public class SyncManager implements OnAccountsUpdateListener {
if (syncResult != null && syncResult.tooManyDeletions) {
installHandleTooManyDeletesNotification(syncOperation.account,
- syncOperation.authority, syncResult.stats.numDeletes);
+ syncOperation.authority, syncResult.stats.numDeletes,
+ syncOperation.userId);
} else {
- mNotificationMgr.cancel(
- syncOperation.account.hashCode() ^ syncOperation.authority.hashCode());
+ mNotificationMgr.cancelAsUser(null,
+ syncOperation.account.hashCode() ^ syncOperation.authority.hashCode(),
+ new UserHandle(syncOperation.userId));
}
if (syncResult != null && syncResult.fullSyncRequested) {
@@ -2466,7 +2524,7 @@ public class SyncManager implements OnAccountsUpdateListener {
}
private void installHandleTooManyDeletesNotification(Account account, String authority,
- long numDeletes) {
+ long numDeletes, int userId) {
if (mNotificationMgr == null) return;
final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider(
@@ -2488,7 +2546,8 @@ public class SyncManager implements OnAccountsUpdateListener {
}
final PendingIntent pendingIntent = PendingIntent
- .getActivity(mContext, 0, clickIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ .getActivityAsUser(mContext, 0, clickIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(userId));
CharSequence tooManyDeletesDescFormat = mContext.getResources().getText(
R.string.contentServiceTooManyDeletesNotificationDesc);
@@ -2502,7 +2561,8 @@ public class SyncManager implements OnAccountsUpdateListener {
String.format(tooManyDeletesDescFormat.toString(), authorityName),
pendingIntent);
notification.flags |= Notification.FLAG_ONGOING_EVENT;
- mNotificationMgr.notify(account.hashCode() ^ authority.hashCode(), notification);
+ mNotificationMgr.notifyAsUser(null, account.hashCode() ^ authority.hashCode(),
+ notification, new UserHandle(userId));
}
/**
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
index 9fcc22d..6611fcd 100644
--- a/core/java/android/content/SyncOperation.java
+++ b/core/java/android/content/SyncOperation.java
@@ -95,13 +95,18 @@ public class SyncOperation implements Comparable {
}
public String dump(boolean useOneLine) {
- StringBuilder sb = new StringBuilder();
- sb.append(account.name);
- sb.append(" (" + account.type + ")");
- sb.append(", " + authority);
- sb.append(", ");
- sb.append(SyncStorageEngine.SOURCES[syncSource]);
- sb.append(", earliestRunTime " + earliestRunTime);
+ StringBuilder sb = new StringBuilder()
+ .append(account.name)
+ .append(" u")
+ .append(userId).append(" (")
+ .append(account.type)
+ .append(")")
+ .append(", ")
+ .append(authority)
+ .append(", ")
+ .append(SyncStorageEngine.SOURCES[syncSource])
+ .append(", earliestRunTime ")
+ .append(earliestRunTime);
if (expedited) {
sb.append(", EXPEDITED");
}
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 7a9fc65..de97481 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -16,7 +16,6 @@
package android.content;
-import com.android.internal.os.AtomicFile;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
@@ -38,7 +37,7 @@ import android.os.Message;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.os.SystemClock;
+import android.util.AtomicFile;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 6b16e74..b884b98 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -172,6 +172,20 @@ public class ActivityInfo extends ComponentInfo
*/
public static final int FLAG_IMMERSIVE = 0x0400;
/**
+ * @hide Bit in {@link #flags}: If set, this component will only be seen
+ * by the primary user. Only works with broadcast receivers. Set from the
+ * {@link android.R.attr#primaryUserOnly} attribute.
+ */
+ public static final int FLAG_PRIMARY_USER_ONLY = 0x20000000;
+ /**
+ * Bit in {@link #flags}: If set, a single instance of the receiver will
+ * run for all users on the device. Set from the
+ * {@link android.R.attr#singleUser} attribute. Note that this flag is
+ * only relevant for ActivityInfo structures that are describing receiver
+ * components; it is not applied to activities.
+ */
+ public static final int FLAG_SINGLE_USER = 0x40000000;
+ /**
* Options that have been set in the activity declaration in the
* manifest.
* These include:
@@ -181,7 +195,7 @@ public class ActivityInfo extends ComponentInfo
* {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS},
* {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY},
* {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS},
- * {@link #FLAG_HARDWARE_ACCELERATED}
+ * {@link #FLAG_HARDWARE_ACCELERATED}, {@link #FLAG_SINGLE_USER}.
*/
public int flags;
@@ -358,6 +372,18 @@ public class ActivityInfo extends ComponentInfo
public static final int CONFIG_SMALLEST_SCREEN_SIZE = 0x0800;
/**
* Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle density changes. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_DENSITY = 0x1000;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle the change to layout direction. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_LAYOUT_DIRECTION = 0x2000;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
* can itself handle changes to the font scaling factor. Set from the
* {@link android.R.attr#configChanges} attribute. This is
* not a core resource configutation, but a higher-level value, so its
@@ -383,6 +409,8 @@ public class ActivityInfo extends ComponentInfo
0x1000, // UI MODE
0x0200, // SCREEN SIZE
0x2000, // SMALLEST SCREEN SIZE
+ 0x0100, // DENSITY
+ 0x4000, // LAYOUT DIRECTION
};
/** @hide
* Convert Java change bits to native.
@@ -419,8 +447,9 @@ public class ActivityInfo extends ComponentInfo
* {@link #CONFIG_MCC}, {@link #CONFIG_MNC},
* {@link #CONFIG_LOCALE}, {@link #CONFIG_TOUCHSCREEN},
* {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION},
- * {@link #CONFIG_ORIENTATION}, and {@link #CONFIG_SCREEN_LAYOUT}. Set from the
- * {@link android.R.attr#configChanges} attribute.
+ * {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT} and
+ * {@link #CONFIG_LAYOUT_DIRECTION}. Set from the {@link android.R.attr#configChanges}
+ * attribute.
*/
public int configChanges;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index e1434b3..a0283d3 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -298,11 +298,23 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* activity's manifest.
*
* Default value is false (no support for RTL).
- * @hide
*/
public static final int FLAG_SUPPORTS_RTL = 1<<22;
/**
+ * Value for {@link #flags}: true if the application is currently
+ * installed for the calling user.
+ */
+ public static final int FLAG_INSTALLED = 1<<23;
+
+ /**
+ * Value for {@link #flags}: true if the application only has its
+ * data installed; the application package itself does not currently
+ * exist on the device.
+ */
+ public static final int FLAG_IS_DATA_ONLY = 1<<24;
+
+ /**
* Value for {@link #flags}: Set to true if the application has been
* installed using the forward lock option.
*
@@ -335,7 +347,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* {@link #FLAG_SUPPORTS_NORMAL_SCREENS},
* {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_SUPPORTS_XLARGE_SCREENS},
* {@link #FLAG_RESIZEABLE_FOR_SCREENS},
- * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE}
+ * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE},
+ * {@link #FLAG_INSTALLED}.
*/
public int flags = 0;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 90b4247..b0ae5da 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -32,6 +32,7 @@ import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ManifestDigest;
+import android.content.pm.PackageCleanItem;
import android.content.pm.ParceledListSlice;
import android.content.pm.ProviderInfo;
import android.content.pm.PermissionGroupInfo;
@@ -39,8 +40,10 @@ import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
+import android.content.pm.VerificationParams;
import android.content.pm.VerifierDeviceIdentity;
import android.net.Uri;
+import android.os.ParcelFileDescriptor;
import android.content.IntentSender;
/**
@@ -124,7 +127,7 @@ interface IPackageManager {
* limit that kicks in when flags are included that bloat up the data
* returned.
*/
- ParceledListSlice getInstalledPackages(int flags, in String lastRead);
+ ParceledListSlice getInstalledPackages(int flags, in String lastRead, in int userId);
/**
* This implements getInstalledApplications via a "last returned row"
@@ -199,7 +202,7 @@ interface IPackageManager {
List<PackageInfo> getPreferredPackages(int flags);
void addPreferredActivity(in IntentFilter filter, int match,
- in ComponentName[] set, in ComponentName activity);
+ in ComponentName[] set, in ComponentName activity, int userId);
void replacePreferredActivity(in IntentFilter filter, int match,
in ComponentName[] set, in ComponentName activity);
@@ -303,10 +306,11 @@ interface IPackageManager {
* Get package statistics including the code, data and cache size for
* an already installed package
* @param packageName The package name of the application
+ * @param userHandle Which user the size should be retrieved for
* @param observer a callback to use to notify when the asynchronous
* retrieval of information is complete.
*/
- void getPackageSizeInfo(in String packageName, IPackageStatsObserver observer);
+ void getPackageSizeInfo(in String packageName, int userHandle, IPackageStatsObserver observer);
/**
* Get a list of shared libraries that are available on the
@@ -348,7 +352,7 @@ interface IPackageManager {
*/
void updateExternalMediaStatus(boolean mounted, boolean reportStatus);
- String nextPackageToClean(String lastPackage);
+ PackageCleanItem nextPackageToClean(in PackageCleanItem lastPackage);
void movePackage(String packageName, IPackageMoveObserver observer, int flags);
@@ -357,23 +361,24 @@ interface IPackageManager {
boolean setInstallLocation(int loc);
int getInstallLocation();
- UserInfo createUser(in String name, int flags);
- boolean removeUser(int userId);
- void updateUserName(int userId, String name);
-
void installPackageWithVerification(in Uri packageURI, in IPackageInstallObserver observer,
int flags, in String installerPackageName, in Uri verificationURI,
in ManifestDigest manifestDigest, in ContainerEncryptionParams encryptionParams);
+ void installPackageWithVerificationAndEncryption(in Uri packageURI,
+ in IPackageInstallObserver observer, int flags, in String installerPackageName,
+ in VerificationParams verificationParams,
+ in ContainerEncryptionParams encryptionParams);
+
+ int installExistingPackage(String packageName);
+
void verifyPendingInstall(int id, int verificationCode);
+ void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay);
VerifierDeviceIdentity getVerifierDeviceIdentity();
boolean isFirstBoot();
- List<UserInfo> getUsers();
- UserInfo getUser(int userId);
-
void setPermissionEnforced(String permission, boolean enforced);
boolean isPermissionEnforced(String permission);
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index ea47e8e..a977e41 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -18,10 +18,6 @@ package android.content.pm;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.TextUtils;
-
-import java.text.Collator;
-import java.util.Comparator;
/**
* Information you can retrieve about a particular piece of test
diff --git a/core/java/android/content/pm/ManifestDigest.java b/core/java/android/content/pm/ManifestDigest.java
index f5e72e0..75505bc 100644
--- a/core/java/android/content/pm/ManifestDigest.java
+++ b/core/java/android/content/pm/ManifestDigest.java
@@ -1,3 +1,19 @@
+/*
+ * 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.Parcel;
diff --git a/core/java/android/content/pm/PackageCleanItem.aidl b/core/java/android/content/pm/PackageCleanItem.aidl
new file mode 100644
index 0000000..9bb203e
--- /dev/null
+++ b/core/java/android/content/pm/PackageCleanItem.aidl
@@ -0,0 +1,18 @@
+/* Copyright 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;
+
+parcelable PackageCleanItem;
diff --git a/core/java/android/content/pm/PackageCleanItem.java b/core/java/android/content/pm/PackageCleanItem.java
new file mode 100644
index 0000000..b1896aa
--- /dev/null
+++ b/core/java/android/content/pm/PackageCleanItem.java
@@ -0,0 +1,85 @@
+/*
+ * 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.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class PackageCleanItem {
+ public final int userId;
+ public final String packageName;
+ public final boolean andCode;
+
+ public PackageCleanItem(int userId, String packageName, boolean andCode) {
+ this.userId = userId;
+ this.packageName = packageName;
+ this.andCode = andCode;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ try {
+ if (obj != null) {
+ PackageCleanItem other = (PackageCleanItem)obj;
+ return userId == other.userId && packageName.equals(other.packageName)
+ && andCode == other.andCode;
+ }
+ } catch (ClassCastException e) {
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + userId;
+ result = 31 * result + packageName.hashCode();
+ result = 31 * result + (andCode ? 1 : 0);
+ return result;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeInt(userId);
+ dest.writeString(packageName);
+ dest.writeInt(andCode ? 1 : 0);
+ }
+
+ public static final Parcelable.Creator<PackageCleanItem> CREATOR
+ = new Parcelable.Creator<PackageCleanItem>() {
+ public PackageCleanItem createFromParcel(Parcel source) {
+ return new PackageCleanItem(source);
+ }
+
+ public PackageCleanItem[] newArray(int size) {
+ return new PackageCleanItem[size];
+ }
+ };
+
+ private PackageCleanItem(Parcel source) {
+ userId = source.readInt();
+ packageName = source.readString();
+ andCode = source.readInt() != 0;
+ }
+}
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index 9625944..a1566da 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -32,6 +32,11 @@ public class PackageInfoLite implements Parcelable {
public String packageName;
/**
+ * The android:versionCode of the package.
+ */
+ public int versionCode;
+
+ /**
* Specifies the recommended install location. Can be one of
* {@link #PackageHelper.RECOMMEND_INSTALL_INTERNAL} to install on internal storage
* {@link #PackageHelper.RECOMMEND_INSTALL_EXTERNAL} to install on external media
@@ -58,6 +63,7 @@ public class PackageInfoLite implements Parcelable {
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeString(packageName);
+ dest.writeInt(versionCode);
dest.writeInt(recommendedInstallLocation);
dest.writeInt(installLocation);
@@ -82,6 +88,7 @@ public class PackageInfoLite implements Parcelable {
private PackageInfoLite(Parcel source) {
packageName = source.readString();
+ versionCode = source.readInt();
recommendedInstallLocation = source.readInt();
installLocation = source.readInt();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 7e96ae6..8ba1988 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -28,6 +28,7 @@ import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Environment;
+import android.os.UserHandle;
import android.util.AndroidException;
import android.util.DisplayMetrics;
@@ -309,6 +310,23 @@ public abstract class PackageManager {
public static final int INSTALL_FROM_ADB = 0x00000020;
/**
+ * Flag parameter for {@link #installPackage} to indicate that this install
+ * should immediately be visible to all users.
+ *
+ * @hide
+ */
+ public static final int INSTALL_ALL_USERS = 0x00000040;
+
+ /**
+ * Flag parameter for {@link #installPackage} to indicate that it is okay
+ * to install an update to an app where the newly installed app has a lower
+ * version code than the currently installed app.
+ *
+ * @hide
+ */
+ public static final int INSTALL_ALLOW_DOWNGRADE = 0x00000080;
+
+ /**
* Flag parameter for
* {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
* that you don't want to kill the app containing the component. Be careful when you set this
@@ -528,6 +546,14 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_UID_CHANGED = -24;
/**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * the new package has an older version code than the currently installed package.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_VERSION_DOWNGRADE = -25;
+
+ /**
* Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
* if the parser was given a path that is not a file, or does not end with the expected
@@ -624,7 +650,15 @@ public abstract class PackageManager {
*
* @hide
*/
- public static final int DONT_DELETE_DATA = 0x00000001;
+ public static final int DELETE_KEEP_DATA = 0x00000001;
+
+ /**
+ * Flag parameter for {@link #deletePackage} to indicate that you want the
+ * package deleted for all users.
+ *
+ * @hide
+ */
+ public static final int DELETE_ALL_USERS = 0x00000002;
/**
* Return code for when package deletion succeeds. This is passed to the
@@ -761,6 +795,14 @@ public abstract class PackageManager {
public static final int VERIFICATION_REJECT = -1;
/**
+ * Can be used as the {@code millisecondsToDelay} argument for
+ * {@link PackageManager#extendVerificationTimeout}. This is the
+ * maximum time {@code PackageManager} waits for the verification
+ * agent to return (in milliseconds).
+ */
+ public static final long MAXIMUM_VERIFICATION_TIMEOUT = 60*60*1000;
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's
* audio pipeline is low-latency, more suitable for audio applications sensitive to delays or
* lag in sound input or output.
@@ -793,6 +835,14 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has at least one camera pointing in
+ * some direction.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_ANY = "android.hardware.camera.any";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device's camera supports flash.
*/
@SdkConstant(SdkConstantType.FEATURE)
@@ -1109,6 +1159,38 @@ public abstract class PackageManager {
= "android.content.pm.extra.VERIFICATION_INSTALL_FLAGS";
/**
+ * Extra field name for the uid of who is requesting to install
+ * the package.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VERIFICATION_INSTALLER_UID
+ = "android.content.pm.extra.VERIFICATION_INSTALLER_UID";
+
+ /**
+ * Extra field name for the package name of a package pending verification.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VERIFICATION_PACKAGE_NAME
+ = "android.content.pm.extra.VERIFICATION_PACKAGE_NAME";
+ /**
+ * Extra field name for the result of a verification, either
+ * {@link #VERIFICATION_ALLOW}, or {@link #VERIFICATION_REJECT}.
+ * Passed to package verifiers after a package is verified.
+ */
+ public static final String EXTRA_VERIFICATION_RESULT
+ = "android.content.pm.extra.VERIFICATION_RESULT";
+
+ /**
+ * Extra field name for the version code of a package pending verification.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VERIFICATION_VERSION_CODE
+ = "android.content.pm.extra.VERIFICATION_VERSION_CODE";
+
+ /**
* Retrieve overall information about an application package that is
* installed on the system.
* <p>
@@ -1419,6 +1501,45 @@ public abstract class PackageManager {
public abstract List<PackageInfo> getInstalledPackages(int flags);
/**
+ * Return a List of all packages that are installed on the device, for a specific user.
+ * Requesting a list of installed packages for another user
+ * will require the permission INTERACT_ACROSS_USERS_FULL.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_ACTIVITIES},
+ * {@link #GET_GIDS},
+ * {@link #GET_CONFIGURATIONS},
+ * {@link #GET_INSTRUMENTATION},
+ * {@link #GET_PERMISSIONS},
+ * {@link #GET_PROVIDERS},
+ * {@link #GET_RECEIVERS},
+ * {@link #GET_SERVICES},
+ * {@link #GET_SIGNATURES},
+ * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
+ * @param userId The user for whom the installed packages are to be listed
+ *
+ * @return A List of PackageInfo objects, one for each package that is
+ * installed on the device. In the unlikely case of there being no
+ * installed packages, an empty list is returned.
+ * If flag GET_UNINSTALLED_PACKAGES is set, a list of all
+ * applications including those deleted with DONT_DELETE_DATA
+ * (partially installed apps with data directory) will be returned.
+ *
+ * @see #GET_ACTIVITIES
+ * @see #GET_GIDS
+ * @see #GET_CONFIGURATIONS
+ * @see #GET_INSTRUMENTATION
+ * @see #GET_PERMISSIONS
+ * @see #GET_PROVIDERS
+ * @see #GET_RECEIVERS
+ * @see #GET_SERVICES
+ * @see #GET_SIGNATURES
+ * @see #GET_UNINSTALLED_PACKAGES
+ *
+ * @hide
+ */
+ public abstract List<PackageInfo> getInstalledPackages(int flags, int userId);
+
+ /**
* Check whether a particular package has been granted a particular
* permission.
*
@@ -1684,6 +1805,39 @@ public abstract class PackageManager {
public abstract ResolveInfo resolveActivity(Intent intent, int flags);
/**
+ * Determine the best action to perform for a given Intent for a given user. This
+ * is how {@link Intent#resolveActivity} finds an activity if a class has not
+ * been explicitly specified.
+ *
+ * <p><em>Note:</em> if using an implicit Intent (without an explicit ComponentName
+ * specified), be sure to consider whether to set the {@link #MATCH_DEFAULT_ONLY}
+ * only flag. You need to do so to resolve the activity in the same way
+ * that {@link android.content.Context#startActivity(Intent)} and
+ * {@link android.content.Intent#resolveActivity(PackageManager)
+ * Intent.resolveActivity(PackageManager)} do.</p>
+ *
+ * @param intent An intent containing all of the desired specification
+ * (action, data, type, category, and/or component).
+ * @param flags Additional option flags. The most important is
+ * {@link #MATCH_DEFAULT_ONLY}, to limit the resolution to only
+ * those activities that support the {@link android.content.Intent#CATEGORY_DEFAULT}.
+ * @param userId The user id.
+ *
+ * @return Returns a ResolveInfo containing the final activity intent that
+ * was determined to be the best action. Returns null if no
+ * matching activity was found. If multiple matching activities are
+ * found and there is no default set, returns a ResolveInfo
+ * containing something else, such as the activity resolver.
+ *
+ * @see #MATCH_DEFAULT_ONLY
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_RESOLVED_FILTER
+ *
+ * @hide
+ */
+ public abstract ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId);
+
+ /**
* Retrieve all activities that can be performed for the given intent.
*
* @param intent The desired intent as per resolveActivity().
@@ -1705,6 +1859,29 @@ public abstract class PackageManager {
int flags);
/**
+ * Retrieve all activities that can be performed for the given intent, for a specific user.
+ *
+ * @param intent The desired intent as per resolveActivity().
+ * @param flags Additional option flags. The most important is
+ * {@link #MATCH_DEFAULT_ONLY}, to limit the resolution to only
+ * those activities that support the {@link android.content.Intent#CATEGORY_DEFAULT}.
+ *
+ * @return A List&lt;ResolveInfo&gt; containing one entry for each matching
+ * Activity. These are ordered from best to worst match -- that
+ * is, the first item in the list is what is returned by
+ * {@link #resolveActivity}. If there are no matching activities, an empty
+ * list is returned.
+ *
+ * @see #MATCH_DEFAULT_ONLY
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_RESOLVED_FILTER
+ * @hide
+ */
+ public abstract List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
+ int flags, int userId);
+
+
+ /**
* Retrieve a set of activities that should be presented to the user as
* similar options. This is like {@link #queryIntentActivities}, except it
* also allows you to supply a list of more explicit Intents that you would
@@ -1754,6 +1931,26 @@ public abstract class PackageManager {
int flags);
/**
+ * Retrieve all receivers that can handle a broadcast of the given intent, for a specific
+ * user.
+ *
+ * @param intent The desired intent as per resolveActivity().
+ * @param flags Additional option flags.
+ * @param userId The userId of the user being queried.
+ *
+ * @return A List&lt;ResolveInfo&gt; containing one entry for each matching
+ * Receiver. These are ordered from first to last in priority. If
+ * there are no matching receivers, an empty list is returned.
+ *
+ * @see #MATCH_DEFAULT_ONLY
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_RESOLVED_FILTER
+ * @hide
+ */
+ public abstract List<ResolveInfo> queryBroadcastReceivers(Intent intent,
+ int flags, int userId);
+
+ /**
* Determine the best service to handle for a given Intent.
*
* @param intent An intent containing all of the desired specification
@@ -1788,6 +1985,27 @@ public abstract class PackageManager {
int flags);
/**
+ * Retrieve all services that can match the given intent for a given user.
+ *
+ * @param intent The desired intent as per resolveService().
+ * @param flags Additional option flags.
+ * @param userId The user id.
+ *
+ * @return A List&lt;ResolveInfo&gt; containing one entry for each matching
+ * ServiceInfo. These are ordered from best to worst match -- that
+ * is, the first item in the list is what is returned by
+ * resolveService(). If there are no matching services, an empty
+ * list is returned.
+ *
+ * @see #GET_INTENT_FILTERS
+ * @see #GET_RESOLVED_FILTER
+ *
+ * @hide
+ */
+ public abstract List<ResolveInfo> queryIntentServicesAsUser(Intent intent,
+ int flags, int userId);
+
+ /**
* Find a single content provider by its base path name.
*
* @param name The name of the provider to find.
@@ -2123,6 +2341,10 @@ public abstract class PackageManager {
public abstract Resources getResourcesForApplication(String appPackageName)
throws NameNotFoundException;
+ /** @hide */
+ public abstract Resources getResourcesForApplicationAsUser(String appPackageName, int userId)
+ throws NameNotFoundException;
+
/**
* Retrieve overall information about an application package defined
* in a package archive file
@@ -2166,8 +2388,8 @@ public abstract class PackageManager {
if ((flags & GET_SIGNATURES) != 0) {
packageParser.collectCertificates(pkg, 0);
}
- return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, false,
- COMPONENT_ENABLED_STATE_DEFAULT);
+ PackageUserState state = new PackageUserState();
+ return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
}
/**
@@ -2227,6 +2449,45 @@ public abstract class PackageManager {
ContainerEncryptionParams encryptionParams);
/**
+ * Similar to
+ * {@link #installPackage(Uri, IPackageInstallObserver, int, String)} but
+ * with an extra verification information provided.
+ *
+ * @param packageURI The location of the package file to install. This can
+ * be a 'file:' or a 'content:' URI.
+ * @param observer An observer callback to get notified when the package
+ * installation is complete.
+ * {@link IPackageInstallObserver#packageInstalled(String, int)}
+ * will be called when that happens. observer may be null to
+ * indicate that no callback is desired.
+ * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
+ * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}
+ * .
+ * @param installerPackageName Optional package name of the application that
+ * is performing the installation. This identifies which market
+ * the package came from.
+ * @param verificationParams an object that holds signal information to
+ * assist verification. May be {@code null}.
+ * @param encryptionParams if the package to be installed is encrypted,
+ * these parameters describing the encryption and authentication
+ * used. May be {@code null}.
+ *
+ * @hide
+ */
+ public abstract void installPackageWithVerificationAndEncryption(Uri packageURI,
+ IPackageInstallObserver observer, int flags, String installerPackageName,
+ VerificationParams verificationParams,
+ ContainerEncryptionParams encryptionParams);
+
+ /**
+ * If there is already an application with the given package name installed
+ * on the system for other users, also install it for the calling user.
+ * @hide
+ */
+ public abstract int installExistingPackage(String packageName)
+ throws NameNotFoundException;
+
+ /**
* Allows a package listening to the
* {@link Intent#ACTION_PACKAGE_NEEDS_VERIFICATION package verification
* broadcast} to respond to the package manager. The response must include
@@ -2238,10 +2499,47 @@ public abstract class PackageManager {
* {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra
* @param verificationCode either {@link PackageManager#VERIFICATION_ALLOW}
* or {@link PackageManager#VERIFICATION_REJECT}.
+ * @throws SecurityException if the caller does not have the
+ * PACKAGE_VERIFICATION_AGENT permission.
*/
public abstract void verifyPendingInstall(int id, int verificationCode);
/**
+ * Allows a package listening to the
+ * {@link Intent#ACTION_PACKAGE_NEEDS_VERIFICATION package verification
+ * broadcast} to extend the default timeout for a response and declare what
+ * action to perform after the timeout occurs. The response must include
+ * the {@code verificationCodeAtTimeout} which is one of
+ * {@link PackageManager#VERIFICATION_ALLOW} or
+ * {@link PackageManager#VERIFICATION_REJECT}.
+ *
+ * This method may only be called once per package id. Additional calls
+ * will have no effect.
+ *
+ * @param id pending package identifier as passed via the
+ * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra
+ * @param verificationCodeAtTimeout either
+ * {@link PackageManager#VERIFICATION_ALLOW} or
+ * {@link PackageManager#VERIFICATION_REJECT}. If
+ * {@code verificationCodeAtTimeout} is neither
+ * {@link PackageManager#VERIFICATION_ALLOW} or
+ * {@link PackageManager#VERIFICATION_REJECT}, then
+ * {@code verificationCodeAtTimeout} will default to
+ * {@link PackageManager#VERIFICATION_REJECT}.
+ * @param millisecondsToDelay the amount of time requested for the timeout.
+ * Must be positive and less than
+ * {@link PackageManager#MAXIMUM_VERIFICATION_TIMEOUT}. If
+ * {@code millisecondsToDelay} is out of bounds,
+ * {@code millisecondsToDelay} will be set to the closest in
+ * bounds value; namely, 0 or
+ * {@link PackageManager#MAXIMUM_VERIFICATION_TIMEOUT}.
+ * @throws SecurityException if the caller does not have the
+ * PACKAGE_VERIFICATION_AGENT permission.
+ */
+ public abstract void extendVerificationTimeout(int id,
+ int verificationCodeAtTimeout, long millisecondsToDelay);
+
+ /**
* Change the installer associated with a given package. There are limitations
* on how the installer package can be changed; in particular:
* <ul>
@@ -2270,7 +2568,8 @@ public abstract class PackageManager {
* @param observer An observer callback to get notified when the package deletion is
* complete. {@link android.content.pm.IPackageDeleteObserver#packageDeleted(boolean)} will be
* called when that happens. observer may be null to indicate that no callback is desired.
- * @param flags - possible values: {@link #DONT_DELETE_DATA}
+ * @param flags - possible values: {@link #DELETE_KEEP_DATA},
+ * {@link #DELETE_ALL_USERS}.
*
* @hide
*/
@@ -2376,6 +2675,7 @@ public abstract class PackageManager {
* should have the {@link android.Manifest.permission#GET_PACKAGE_SIZE} permission.
*
* @param packageName The name of the package whose size information is to be retrieved
+ * @param userHandle The user whose size information should be retrieved.
* @param observer An observer callback to get notified when the operation
* is complete.
* {@link android.content.pm.IPackageStatsObserver#onGetStatsCompleted(PackageStats, boolean)}
@@ -2386,10 +2686,20 @@ public abstract class PackageManager {
*
* @hide
*/
- public abstract void getPackageSizeInfo(String packageName,
+ public abstract void getPackageSizeInfo(String packageName, int userHandle,
IPackageStatsObserver observer);
/**
+ * Like {@link #getPackageSizeInfo(String, int, IPackageStatsObserver)}, but
+ * returns the size for the calling user.
+ *
+ * @hide
+ */
+ public void getPackageSizeInfo(String packageName, IPackageStatsObserver observer) {
+ getPackageSizeInfo(packageName, UserHandle.myUserId(), observer);
+ }
+
+ /**
* @deprecated This function no longer does anything; it was an old
* approach to managing preferred activities, which has been superceeded
* (and conflicts with) the modern activity-based preferences.
@@ -2460,6 +2770,17 @@ public abstract class PackageManager {
ComponentName[] set, ComponentName activity);
/**
+ * Same as {@link #addPreferredActivity(IntentFilter, int,
+ ComponentName[], ComponentName)}, but with a specific userId to apply the preference
+ to.
+ * @hide
+ */
+ public void addPreferredActivity(IntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity, int userId) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* @deprecated This is a protected API that should not have been available
* to third party applications. It is the platform's responsibility for
* assigning preferred activities and this can not be directly modified.
@@ -2616,56 +2937,6 @@ public abstract class PackageManager {
String packageName, IPackageMoveObserver observer, int flags);
/**
- * Creates a user with the specified name and options.
- *
- * @param name the user's name
- * @param flags flags that identify the type of user and other properties.
- * @see UserInfo
- *
- * @return the UserInfo object for the created user, or null if the user could not be created.
- * @hide
- */
- public abstract UserInfo createUser(String name, int flags);
-
- /**
- * @return the list of users that were created
- * @hide
- */
- public abstract List<UserInfo> getUsers();
-
- /**
- * @param id the ID of the user, where 0 is the primary user.
- * @hide
- */
- public abstract boolean removeUser(int id);
-
- /**
- * Updates the user's name.
- *
- * @param id the user's id
- * @param name the new name for the user
- * @hide
- */
- public abstract void updateUserName(int id, String name);
-
- /**
- * Changes the user's properties specified by the flags.
- *
- * @param id the user's id
- * @param flags the new flags for the user
- * @hide
- */
- public abstract void updateUserFlags(int id, int flags);
-
- /**
- * Returns the details for the user specified by userId.
- * @param userId the user id of the user
- * @return UserInfo for the specified user, or null if no such user exists.
- * @hide
- */
- public abstract UserInfo getUser(int userId);
-
- /**
* 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.
*
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index f8898c1..c2b75f4 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -28,7 +28,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.PatternMatcher;
-import android.os.UserId;
+import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Base64;
import android.util.DisplayMetrics;
@@ -203,11 +203,14 @@ public class PackageParser {
*/
public static class PackageLite {
public final String packageName;
+ public final int versionCode;
public final int installLocation;
public final VerifierInfo[] verifiers;
- public PackageLite(String packageName, int installLocation, List<VerifierInfo> verifiers) {
+ public PackageLite(String packageName, int versionCode,
+ int installLocation, List<VerifierInfo> verifiers) {
this.packageName = packageName;
+ this.versionCode = versionCode;
this.installLocation = installLocation;
this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
}
@@ -243,14 +246,15 @@ public class PackageParser {
return name.endsWith(".apk");
}
+ /*
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
HashSet<String> grantedPermissions) {
-
+ PackageUserState state = new PackageUserState();
return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
- grantedPermissions, false, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
- UserId.getCallingUserId());
+ grantedPermissions, state, UserHandle.getCallingUserId());
}
+ */
/**
* Generate and return the {@link PackageInfo} for a parsed package.
@@ -260,23 +264,30 @@ public class PackageParser {
*/
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
- HashSet<String> grantedPermissions, boolean stopped, int enabledState) {
+ HashSet<String> grantedPermissions, PackageUserState state) {
return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
- grantedPermissions, stopped, enabledState, UserId.getCallingUserId());
+ grantedPermissions, state, UserHandle.getCallingUserId());
+ }
+
+ private static boolean checkUseInstalled(int flags, PackageUserState state) {
+ return state.installed || ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0);
}
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
- HashSet<String> grantedPermissions, boolean stopped, int enabledState, int userId) {
+ HashSet<String> grantedPermissions, PackageUserState state, int userId) {
+ if (!checkUseInstalled(flags, state)) {
+ return null;
+ }
PackageInfo pi = new PackageInfo();
pi.packageName = p.packageName;
pi.versionCode = p.mVersionCode;
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
- pi.applicationInfo = generateApplicationInfo(p, flags, stopped, enabledState, userId);
+ pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
pi.installLocation = p.installLocation;
pi.firstInstallTime = firstInstallTime;
pi.lastUpdateTime = lastUpdateTime;
@@ -312,7 +323,7 @@ public class PackageParser {
if (activity.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.activities[j++] = generateActivityInfo(p.activities.get(i), flags,
- stopped, enabledState, userId);
+ state, userId);
}
}
}
@@ -334,7 +345,7 @@ public class PackageParser {
if (activity.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags,
- stopped, enabledState, userId);
+ state, userId);
}
}
}
@@ -355,8 +366,8 @@ public class PackageParser {
final Service service = p.services.get(i);
if (service.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.services[j++] = generateServiceInfo(p.services.get(i), flags, stopped,
- enabledState, userId);
+ pi.services[j++] = generateServiceInfo(p.services.get(i), flags,
+ state, userId);
}
}
}
@@ -377,8 +388,8 @@ public class PackageParser {
final Provider provider = p.providers.get(i);
if (provider.info.enabled
|| (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
- pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags, stopped,
- enabledState, userId);
+ pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags,
+ state, userId);
}
}
}
@@ -840,11 +851,19 @@ public class PackageParser {
return null;
}
int installLocation = PARSE_DEFAULT_INSTALL_LOCATION;
+ int versionCode = 0;
+ int numFound = 0;
for (int i = 0; i < attrs.getAttributeCount(); i++) {
String attr = attrs.getAttributeName(i);
if (attr.equals("installLocation")) {
installLocation = attrs.getAttributeIntValue(i,
PARSE_DEFAULT_INSTALL_LOCATION);
+ numFound++;
+ } else if (attr.equals("versionCode")) {
+ versionCode = attrs.getAttributeIntValue(i, 0);
+ numFound++;
+ }
+ if (numFound >= 2) {
break;
}
}
@@ -867,7 +886,7 @@ public class PackageParser {
}
}
- return new PackageLite(pkgName.intern(), installLocation, verifiers);
+ return new PackageLite(pkgName.intern(), versionCode, installLocation, verifiers);
}
/**
@@ -1468,7 +1487,8 @@ public class PackageParser {
perm.info.descriptionRes = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestPermissionGroup_description,
0);
- perm.info.flags = 0;
+ perm.info.flags = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags, 0);
perm.info.priority = sa.getInt(
com.android.internal.R.styleable.AndroidManifestPermissionGroup_priority, 0);
if (perm.info.priority > 0 && (flags&PARSE_IS_SYSTEM) == 0) {
@@ -1523,6 +1543,9 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestPermission_protectionLevel,
PermissionInfo.PROTECTION_NORMAL);
+ perm.info.flags = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestPermission_permissionFlags, 0);
+
sa.recycle();
if (perm.info.protectionLevel == -1) {
@@ -2040,7 +2063,7 @@ public class PackageParser {
return null;
}
- final boolean setExported = sa.hasValue(
+ boolean setExported = sa.hasValue(
com.android.internal.R.styleable.AndroidManifestActivity_exported);
if (setExported) {
a.info.exported = sa.getBoolean(
@@ -2166,6 +2189,26 @@ public class PackageParser {
a.info.configChanges = 0;
}
+ if (receiver) {
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_singleUser,
+ false)) {
+ a.info.flags |= ActivityInfo.FLAG_SINGLE_USER;
+ if (a.info.exported) {
+ Slog.w(TAG, "Activity exported request ignored due to singleUser: "
+ + a.className + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ a.info.exported = false;
+ }
+ setExported = true;
+ }
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestActivity_primaryUserOnly,
+ false)) {
+ a.info.flags |= ActivityInfo.FLAG_PRIMARY_USER_ONLY;
+ }
+ }
+
sa.recycle();
if (receiver && (owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
@@ -2428,8 +2471,18 @@ public class PackageParser {
return null;
}
+ boolean providerExportedDefault = false;
+
+ if (owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // For compatibility, applications targeting API level 16 or lower
+ // should have their content providers exported by default, unless they
+ // specify otherwise.
+ providerExportedDefault = true;
+ }
+
p.info.exported = sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestProvider_exported, true);
+ com.android.internal.R.styleable.AndroidManifestProvider_exported,
+ providerExportedDefault);
String cpname = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestProvider_authorities, 0);
@@ -2475,6 +2528,20 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestProvider_initOrder,
0);
+ p.info.flags = 0;
+
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestProvider_singleUser,
+ false)) {
+ p.info.flags |= ProviderInfo.FLAG_SINGLE_USER;
+ if (p.info.exported) {
+ Slog.w(TAG, "Provider exported request ignored due to singleUser: "
+ + p.className + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ p.info.exported = false;
+ }
+ }
+
sa.recycle();
if ((owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
@@ -2487,7 +2554,7 @@ public class PackageParser {
}
if (cpname == null) {
- outError[0] = "<provider> does not incude authorities attribute";
+ outError[0] = "<provider> does not include authorities attribute";
return null;
}
p.info.authority = cpname.intern();
@@ -2703,7 +2770,7 @@ public class PackageParser {
return null;
}
- final boolean setExported = sa.hasValue(
+ boolean setExported = sa.hasValue(
com.android.internal.R.styleable.AndroidManifestService_exported);
if (setExported) {
s.info.exported = sa.getBoolean(
@@ -2729,6 +2796,18 @@ public class PackageParser {
false)) {
s.info.flags |= ServiceInfo.FLAG_ISOLATED_PROCESS;
}
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestService_singleUser,
+ false)) {
+ s.info.flags |= ServiceInfo.FLAG_SINGLE_USER;
+ if (s.info.exported) {
+ Slog.w(TAG, "Service exported request ignored due to singleUser: "
+ + s.className + " at " + mArchiveSourcePath + " "
+ + parser.getPositionDescription());
+ s.info.exported = false;
+ }
+ setExported = true;
+ }
sa.recycle();
@@ -3407,13 +3486,25 @@ public class PackageParser {
}
}
- private static boolean copyNeeded(int flags, Package p, int enabledState, Bundle metaData) {
- if (enabledState != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
- boolean enabled = enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ private static boolean copyNeeded(int flags, Package p,
+ PackageUserState state, Bundle metaData, int userId) {
+ if (userId != 0) {
+ // We always need to copy for other users, since we need
+ // to fix up the uid.
+ return true;
+ }
+ if (state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+ boolean enabled = state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
if (p.applicationInfo.enabled != enabled) {
return true;
}
}
+ if (!state.installed) {
+ return true;
+ }
+ if (state.stopped) {
+ return true;
+ }
if ((flags & PackageManager.GET_META_DATA) != 0
&& (metaData != null || p.mAppMetaData != null)) {
return true;
@@ -3425,39 +3516,41 @@ public class PackageParser {
return false;
}
- public static ApplicationInfo generateApplicationInfo(Package p, int flags, boolean stopped,
- int enabledState) {
- return generateApplicationInfo(p, flags, stopped, enabledState, UserId.getCallingUserId());
+ public static ApplicationInfo generateApplicationInfo(Package p, int flags,
+ PackageUserState state) {
+ return generateApplicationInfo(p, flags, state, UserHandle.getCallingUserId());
}
public static ApplicationInfo generateApplicationInfo(Package p, int flags,
- boolean stopped, int enabledState, int userId) {
+ PackageUserState state, int userId) {
if (p == null) return null;
- if (!copyNeeded(flags, p, enabledState, null) && userId == 0) {
+ if (!checkUseInstalled(flags, state)) {
+ return null;
+ }
+ if (!copyNeeded(flags, p, state, null, userId)) {
// CompatibilityMode is global state. It's safe to modify the instance
// of the package.
if (!sCompatibilityModeEnabled) {
p.applicationInfo.disableCompatibilityMode();
}
- if (stopped) {
- p.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED;
- } else {
- p.applicationInfo.flags &= ~ApplicationInfo.FLAG_STOPPED;
- }
- if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ // Make sure we report as installed. Also safe to do, since the
+ // default state should be installed (we will always copy if we
+ // need to report it is not installed).
+ p.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
+ if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
p.applicationInfo.enabled = true;
- } else if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
- || enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+ } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
p.applicationInfo.enabled = false;
}
- p.applicationInfo.enabledSetting = enabledState;
+ p.applicationInfo.enabledSetting = state.enabled;
return p.applicationInfo;
}
// Make shallow copy so we can store the metadata/libraries safely
ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
if (userId != 0) {
- ai.uid = UserId.getUid(userId, ai.uid);
+ ai.uid = UserHandle.getUid(userId, ai.uid);
ai.dataDir = PackageManager.getDataDirForUser(userId, ai.packageName);
}
if ((flags & PackageManager.GET_META_DATA) != 0) {
@@ -3469,18 +3562,23 @@ public class PackageParser {
if (!sCompatibilityModeEnabled) {
ai.disableCompatibilityMode();
}
- if (stopped) {
+ if (state.stopped) {
ai.flags |= ApplicationInfo.FLAG_STOPPED;
} else {
ai.flags &= ~ApplicationInfo.FLAG_STOPPED;
}
- if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ if (state.installed) {
+ ai.flags |= ApplicationInfo.FLAG_INSTALLED;
+ } else {
+ ai.flags &= ~ApplicationInfo.FLAG_INSTALLED;
+ }
+ if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
ai.enabled = true;
- } else if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
- || enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+ } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
ai.enabled = false;
}
- ai.enabledSetting = enabledState;
+ ai.enabledSetting = state.enabled;
return ai;
}
@@ -3527,16 +3625,19 @@ public class PackageParser {
}
}
- public static final ActivityInfo generateActivityInfo(Activity a, int flags, boolean stopped,
- int enabledState, int userId) {
+ public static final ActivityInfo generateActivityInfo(Activity a, int flags,
+ PackageUserState state, int userId) {
if (a == null) return null;
- if (!copyNeeded(flags, a.owner, enabledState, a.metaData) && userId == 0) {
+ if (!checkUseInstalled(flags, state)) {
+ return null;
+ }
+ if (!copyNeeded(flags, a.owner, state, a.metaData, userId)) {
return a.info;
}
// Make shallow copies so we can store the metadata safely
ActivityInfo ai = new ActivityInfo(a.info);
ai.metaData = a.metaData;
- ai.applicationInfo = generateApplicationInfo(a.owner, flags, stopped, enabledState, userId);
+ ai.applicationInfo = generateApplicationInfo(a.owner, flags, state, userId);
return ai;
}
@@ -3561,17 +3662,19 @@ public class PackageParser {
}
}
- public static final ServiceInfo generateServiceInfo(Service s, int flags, boolean stopped,
- int enabledState, int userId) {
+ public static final ServiceInfo generateServiceInfo(Service s, int flags,
+ PackageUserState state, int userId) {
if (s == null) return null;
- if (!copyNeeded(flags, s.owner, enabledState, s.metaData)
- && userId == UserId.getUserId(s.info.applicationInfo.uid)) {
+ if (!checkUseInstalled(flags, state)) {
+ return null;
+ }
+ if (!copyNeeded(flags, s.owner, state, s.metaData, userId)) {
return s.info;
}
// Make shallow copies so we can store the metadata safely
ServiceInfo si = new ServiceInfo(s.info);
si.metaData = s.metaData;
- si.applicationInfo = generateApplicationInfo(s.owner, flags, stopped, enabledState, userId);
+ si.applicationInfo = generateApplicationInfo(s.owner, flags, state, userId);
return si;
}
@@ -3604,13 +3707,15 @@ public class PackageParser {
}
}
- public static final ProviderInfo generateProviderInfo(Provider p, int flags, boolean stopped,
- int enabledState, int userId) {
+ public static final ProviderInfo generateProviderInfo(Provider p, int flags,
+ PackageUserState state, int userId) {
if (p == null) return null;
- if (!copyNeeded(flags, p.owner, enabledState, p.metaData)
+ if (!checkUseInstalled(flags, state)) {
+ return null;
+ }
+ if (!copyNeeded(flags, p.owner, state, p.metaData, userId)
&& ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0
- || p.info.uriPermissionPatterns == null)
- && userId == 0) {
+ || p.info.uriPermissionPatterns == null)) {
return p.info;
}
// Make shallow copies so we can store the metadata safely
@@ -3619,7 +3724,7 @@ public class PackageParser {
if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
pi.uriPermissionPatterns = null;
}
- pi.applicationInfo = generateApplicationInfo(p.owner, flags, stopped, enabledState, userId);
+ pi.applicationInfo = generateApplicationInfo(p.owner, flags, state, userId);
return pi;
}
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
index 1205da7..cb9039b 100644
--- a/core/java/android/content/pm/PackageStats.java
+++ b/core/java/android/content/pm/PackageStats.java
@@ -18,6 +18,7 @@ package android.content.pm;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
/**
* implementation of PackageStats associated with a
@@ -27,6 +28,9 @@ public class PackageStats implements Parcelable {
/** Name of the package to which this stats applies. */
public String packageName;
+ /** @hide */
+ public int userHandle;
+
/** Size of the code (e.g., APK) */
public long codeSize;
@@ -78,33 +82,58 @@ public class PackageStats implements Parcelable {
public String toString() {
final StringBuilder sb = new StringBuilder("PackageStats{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" packageName=");
+ sb.append(" ");
sb.append(packageName);
- sb.append(",codeSize=");
- sb.append(codeSize);
- sb.append(",dataSize=");
- sb.append(dataSize);
- sb.append(",cacheSize=");
- sb.append(cacheSize);
- sb.append(",externalCodeSize=");
- sb.append(externalCodeSize);
- sb.append(",externalDataSize=");
- sb.append(externalDataSize);
- sb.append(",externalCacheSize=");
- sb.append(externalCacheSize);
- sb.append(",externalMediaSize=");
- sb.append(externalMediaSize);
- sb.append(",externalObbSize=");
- sb.append(externalObbSize);
+ if (codeSize != 0) {
+ sb.append(" code=");
+ sb.append(codeSize);
+ }
+ if (dataSize != 0) {
+ sb.append(" data=");
+ sb.append(dataSize);
+ }
+ if (cacheSize != 0) {
+ sb.append(" cache=");
+ sb.append(cacheSize);
+ }
+ if (externalCodeSize != 0) {
+ sb.append(" extCode=");
+ sb.append(externalCodeSize);
+ }
+ if (externalDataSize != 0) {
+ sb.append(" extData=");
+ sb.append(externalDataSize);
+ }
+ if (externalCacheSize != 0) {
+ sb.append(" extCache=");
+ sb.append(externalCacheSize);
+ }
+ if (externalMediaSize != 0) {
+ sb.append(" media=");
+ sb.append(externalMediaSize);
+ }
+ if (externalObbSize != 0) {
+ sb.append(" obb=");
+ sb.append(externalObbSize);
+ }
+ sb.append("}");
return sb.toString();
}
public PackageStats(String pkgName) {
packageName = pkgName;
+ userHandle = UserHandle.myUserId();
+ }
+
+ /** @hide */
+ public PackageStats(String pkgName, int userHandle) {
+ this.packageName = pkgName;
+ this.userHandle = userHandle;
}
public PackageStats(Parcel source) {
packageName = source.readString();
+ userHandle = source.readInt();
codeSize = source.readLong();
dataSize = source.readLong();
cacheSize = source.readLong();
@@ -117,6 +146,7 @@ public class PackageStats implements Parcelable {
public PackageStats(PackageStats pStats) {
packageName = pStats.packageName;
+ userHandle = pStats.userHandle;
codeSize = pStats.codeSize;
dataSize = pStats.dataSize;
cacheSize = pStats.cacheSize;
@@ -133,6 +163,7 @@ public class PackageStats implements Parcelable {
public void writeToParcel(Parcel dest, int parcelableFlags){
dest.writeString(packageName);
+ dest.writeInt(userHandle);
dest.writeLong(codeSize);
dest.writeLong(dataSize);
dest.writeLong(cacheSize);
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
new file mode 100644
index 0000000..7b3d8cd
--- /dev/null
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -0,0 +1,59 @@
+/*
+ * 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 static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+
+import java.util.HashSet;
+
+/**
+ * Per-user state information about a package.
+ * @hide
+ */
+public class PackageUserState {
+ public boolean stopped;
+ public boolean notLaunched;
+ public boolean installed;
+ public int enabled;
+
+ public HashSet<String> disabledComponents;
+ public HashSet<String> enabledComponents;
+
+ public PackageUserState() {
+ this(true);
+ }
+
+ /** @hide */
+ public PackageUserState(boolean isSystem) {
+ if (!isSystem) {
+ stopped = notLaunched = true;
+ }
+ installed = true;
+ enabled = COMPONENT_ENABLED_STATE_DEFAULT;
+ }
+
+ public PackageUserState(PackageUserState o) {
+ installed = o.installed;
+ stopped = o.stopped;
+ notLaunched = o.notLaunched;
+ enabled = o.enabled;
+ disabledComponents = o.disabledComponents != null
+ ? new HashSet<String>(o.disabledComponents) : null;
+ enabledComponents = o.enabledComponents != null
+ ? new HashSet<String>(o.enabledComponents) : null;
+ }
+} \ No newline at end of file
diff --git a/core/java/android/content/pm/PermissionGroupInfo.java b/core/java/android/content/pm/PermissionGroupInfo.java
index 96d30d4..452bf0d 100644
--- a/core/java/android/content/pm/PermissionGroupInfo.java
+++ b/core/java/android/content/pm/PermissionGroupInfo.java
@@ -44,20 +44,17 @@ public class PermissionGroupInfo extends PackageItemInfo implements Parcelable {
/**
* Flag for {@link #flags}, corresponding to <code>personalInfo</code>
* value of {@link android.R.attr#permissionGroupFlags}.
- * @hide
*/
public static final int FLAG_PERSONAL_INFO = 1<<0;
/**
* Additional flags about this group as given by
* {@link android.R.attr#permissionGroupFlags}.
- * @hide
*/
public int flags;
/**
* Prioritization of this group, for visually sorting with other groups.
- * @hide
*/
public int priority;
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 69b812c..5a63e5f 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -79,11 +79,34 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
public static final int PROTECTION_MASK_FLAGS = 0xf0;
/**
+ * The level of access this permission is protecting, as per
+ * {@link android.R.attr#protectionLevel}. Values may be
+ * {@link #PROTECTION_NORMAL}, {@link #PROTECTION_DANGEROUS}, or
+ * {@link #PROTECTION_SIGNATURE}. May also include the additional
+ * flags {@link #PROTECTION_FLAG_SYSTEM} or {@link #PROTECTION_FLAG_DEVELOPMENT}
+ * (which only make sense in combination with the base
+ * {@link #PROTECTION_SIGNATURE}.
+ */
+ public int protectionLevel;
+
+ /**
* The group this permission is a part of, as per
* {@link android.R.attr#permissionGroup}.
*/
public String group;
-
+
+ /**
+ * Flag for {@link #flags}, corresponding to <code>costsMoney</code>
+ * value of {@link android.R.attr#permissionFlags}.
+ */
+ public static final int FLAG_COSTS_MONEY = 1<<0;
+
+ /**
+ * Additional flags about this permission as given by
+ * {@link android.R.attr#permissionFlags}.
+ */
+ public int flags;
+
/**
* A string resource identifier (in the package's resources) of this
* permission's description. From the "description" attribute or,
@@ -99,17 +122,6 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
*/
public CharSequence nonLocalizedDescription;
- /**
- * The level of access this permission is protecting, as per
- * {@link android.R.attr#protectionLevel}. Values may be
- * {@link #PROTECTION_NORMAL}, {@link #PROTECTION_DANGEROUS}, or
- * {@link #PROTECTION_SIGNATURE}. May also include the additional
- * flags {@link #PROTECTION_FLAG_SYSTEM} or {@link #PROTECTION_FLAG_DEVELOPMENT}
- * (which only make sense in combination with the base
- * {@link #PROTECTION_SIGNATURE}.
- */
- public int protectionLevel;
-
/** @hide */
public static int fixProtectionLevel(int level) {
if (level == PROTECTION_SIGNATURE_OR_SYSTEM) {
@@ -149,9 +161,10 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
public PermissionInfo(PermissionInfo orig) {
super(orig);
+ protectionLevel = orig.protectionLevel;
+ flags = orig.flags;
group = orig.group;
descriptionRes = orig.descriptionRes;
- protectionLevel = orig.protectionLevel;
nonLocalizedDescription = orig.nonLocalizedDescription;
}
@@ -191,9 +204,10 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
public void writeToParcel(Parcel dest, int parcelableFlags) {
super.writeToParcel(dest, parcelableFlags);
+ dest.writeInt(protectionLevel);
+ dest.writeInt(flags);
dest.writeString(group);
dest.writeInt(descriptionRes);
- dest.writeInt(protectionLevel);
TextUtils.writeToParcel(nonLocalizedDescription, dest, parcelableFlags);
}
@@ -209,9 +223,10 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable {
private PermissionInfo(Parcel source) {
super(source);
+ protectionLevel = source.readInt();
+ flags = source.readInt();
group = source.readString();
descriptionRes = source.readInt();
- protectionLevel = source.readInt();
nonLocalizedDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
}
}
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index ec01775..a534176 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -73,7 +73,21 @@ public final class ProviderInfo extends ComponentInfo
/** Used to control initialization order of single-process providers
* running in the same process. Higher goes first. */
public int initOrder = 0;
-
+
+ /**
+ * Bit in {@link #flags}: If set, a single instance of the provider will
+ * run for all users on the device. Set from the
+ * {@link android.R.attr#singleUser} attribute.
+ */
+ public static final int FLAG_SINGLE_USER = 0x40000000;
+
+ /**
+ * Options that have been set in the provider declaration in the
+ * manifest.
+ * These include: {@link #FLAG_SINGLE_USER}.
+ */
+ public int flags = 0;
+
/**
* Whether or not this provider is syncable.
* @deprecated This flag is now being ignored. The current way to make a provider
@@ -95,6 +109,7 @@ public final class ProviderInfo extends ComponentInfo
pathPermissions = orig.pathPermissions;
multiprocess = orig.multiprocess;
initOrder = orig.initOrder;
+ flags = orig.flags;
isSyncable = orig.isSyncable;
}
@@ -112,6 +127,7 @@ public final class ProviderInfo extends ComponentInfo
out.writeTypedArray(pathPermissions, parcelableFlags);
out.writeInt(multiprocess ? 1 : 0);
out.writeInt(initOrder);
+ out.writeInt(flags);
out.writeInt(isSyncable ? 1 : 0);
}
@@ -127,8 +143,7 @@ public final class ProviderInfo extends ComponentInfo
};
public String toString() {
- return "ContentProviderInfo{name=" + authority + " className=" + name
- + " isSyncable=" + (isSyncable ? "true" : "false") + "}";
+ return "ContentProviderInfo{name=" + authority + " className=" + name + "}";
}
private ProviderInfo(Parcel in) {
@@ -141,6 +156,7 @@ public final class ProviderInfo extends ComponentInfo
pathPermissions = in.createTypedArray(PathPermission.CREATOR);
multiprocess = in.readInt() != 0;
initOrder = in.readInt();
+ flags = in.readInt();
isSyncable = in.readInt() != 0;
}
}
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index d8f9204..7642670 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -26,6 +26,7 @@ import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Environment;
import android.os.Handler;
+import android.util.AtomicFile;
import android.util.Log;
import android.util.AttributeSet;
import android.util.Xml;
@@ -44,7 +45,6 @@ import java.io.PrintWriter;
import java.io.IOException;
import java.io.FileInputStream;
-import com.android.internal.os.AtomicFile;
import com.android.internal.util.FastXmlSerializer;
import com.google.android.collect.Maps;
@@ -331,12 +331,16 @@ public abstract class RegisteredServicesCache<V> {
notifyListener(v1, true /* removed */);
}
if (changes.length() > 0) {
- Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
- serviceInfos.size() + " services:\n" + changes);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
+ serviceInfos.size() + " services:\n" + changes);
+ }
writePersistentServicesLocked();
} else {
- Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
- serviceInfos.size() + " services unchanged");
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.d(TAG, "generateServicesMap(" + mInterfaceName + "): " +
+ serviceInfos.size() + " services unchanged");
+ }
}
mPersistentServicesFileDidNotExist = false;
}
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index e3749b4..07117fe 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -230,6 +230,21 @@ public class ResolveInfo implements Parcelable {
public ResolveInfo() {
}
+ public ResolveInfo(ResolveInfo orig) {
+ activityInfo = orig.activityInfo;
+ serviceInfo = orig.serviceInfo;
+ filter = orig.filter;
+ priority = orig.priority;
+ preferredOrder = orig.preferredOrder;
+ match = orig.match;
+ specificIndex = orig.specificIndex;
+ labelRes = orig.labelRes;
+ nonLocalizedLabel = orig.nonLocalizedLabel;
+ icon = orig.icon;
+ resolvePackageName = orig.resolvePackageName;
+ system = orig.system;
+ }
+
public String toString() {
ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo;
return "ResolveInfo{"
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 7ee84ab..796c2a4 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -49,10 +49,18 @@ public class ServiceInfo extends ComponentInfo
public static final int FLAG_ISOLATED_PROCESS = 0x0002;
/**
+ * Bit in {@link #flags}: If set, a single instance of the service will
+ * run for all users on the device. Set from the
+ * {@link android.R.attr#singleUser} attribute.
+ */
+ public static final int FLAG_SINGLE_USER = 0x40000000;
+
+ /**
* Options that have been set in the service declaration in the
* manifest.
* These include:
- * {@link #FLAG_STOP_WITH_TASK}, {@link #FLAG_ISOLATED_PROCESS}.
+ * {@link #FLAG_STOP_WITH_TASK}, {@link #FLAG_ISOLATED_PROCESS},
+ * {@link #FLAG_SINGLE_USER}.
*/
public int flags;
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index ba5331c..593f826 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -18,12 +18,23 @@ package android.content.pm;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
/**
* Per-user information.
* @hide
*/
public class UserInfo implements Parcelable {
+
+ /** 6 bits for user type */
+ public static final int FLAG_MASK_USER_TYPE = 0x0000003F;
+
+ /**
+ * *************************** NOTE ***************************
+ * These flag values CAN NOT CHANGE because they are written
+ * directly to storage.
+ */
+
/**
* Primary user. Only one user can have this flag set. Meaning of this
* flag TBD.
@@ -41,14 +52,37 @@ public class UserInfo implements Parcelable {
*/
public static final int FLAG_GUEST = 0x00000004;
+ /**
+ * Indicates the user has restrictions in privileges, in addition to those for normal users.
+ * Exact meaning TBD. For instance, maybe they can't install apps or administer WiFi access pts.
+ */
+ public static final int FLAG_RESTRICTED = 0x00000008;
+
+ /**
+ * Indicates that this user has gone through its first-time initialization.
+ */
+ public static final int FLAG_INITIALIZED = 0x00000010;
+
public int id;
+ public int serialNumber;
public String name;
+ public String iconPath;
public int flags;
+ public long creationTime;
+ public long lastLoggedInTime;
+
+ /** User is only partially created. */
+ public boolean partial;
public UserInfo(int id, String name, int flags) {
+ this(id, name, null, flags);
+ }
+
+ public UserInfo(int id, String name, String iconPath, int flags) {
this.id = id;
this.name = name;
this.flags = flags;
+ this.iconPath = iconPath;
}
public boolean isPrimary() {
@@ -68,8 +102,17 @@ public class UserInfo implements Parcelable {
public UserInfo(UserInfo orig) {
name = orig.name;
+ iconPath = orig.iconPath;
id = orig.id;
flags = orig.flags;
+ serialNumber = orig.serialNumber;
+ creationTime = orig.creationTime;
+ lastLoggedInTime = orig.lastLoggedInTime;
+ partial = orig.partial;
+ }
+
+ public UserHandle getUserHandle() {
+ return new UserHandle(id);
}
@Override
@@ -84,7 +127,12 @@ public class UserInfo implements Parcelable {
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeInt(id);
dest.writeString(name);
+ dest.writeString(iconPath);
dest.writeInt(flags);
+ dest.writeInt(serialNumber);
+ dest.writeLong(creationTime);
+ dest.writeLong(lastLoggedInTime);
+ dest.writeInt(partial ? 1 : 0);
}
public static final Parcelable.Creator<UserInfo> CREATOR
@@ -100,6 +148,11 @@ public class UserInfo implements Parcelable {
private UserInfo(Parcel source) {
id = source.readInt();
name = source.readString();
+ iconPath = source.readString();
flags = source.readInt();
+ serialNumber = source.readInt();
+ creationTime = source.readLong();
+ lastLoggedInTime = source.readLong();
+ partial = source.readInt() != 0;
}
}
diff --git a/core/java/android/content/pm/VerificationParams.aidl b/core/java/android/content/pm/VerificationParams.aidl
new file mode 100644
index 0000000..5bb7f69
--- /dev/null
+++ b/core/java/android/content/pm/VerificationParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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;
+
+parcelable VerificationParams;
diff --git a/core/java/android/content/pm/VerificationParams.java b/core/java/android/content/pm/VerificationParams.java
new file mode 100644
index 0000000..22e1a85
--- /dev/null
+++ b/core/java/android/content/pm/VerificationParams.java
@@ -0,0 +1,236 @@
+/*
+ * 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.content.pm.ManifestDigest;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents verification parameters used to verify packages to be installed.
+ *
+ * @hide
+ */
+public class VerificationParams implements Parcelable {
+ /** A constant used to indicate that a uid value is not present. */
+ public static final int NO_UID = -1;
+
+ /** What we print out first when toString() is called. */
+ private static final String TO_STRING_PREFIX = "VerificationParams{";
+
+ /** The location of the supplementary verification file. */
+ private final Uri mVerificationURI;
+
+ /** URI referencing where the package was downloaded from. */
+ private final Uri mOriginatingURI;
+
+ /** HTTP referrer URI associated with the originatingURI. */
+ private final Uri mReferrer;
+
+ /** UID of the application that the install request originated from. */
+ private final int mOriginatingUid;
+
+ /** UID of application requesting the install */
+ private int mInstallerUid;
+
+ /**
+ * An object that holds the digest of the package which can be used to
+ * verify ownership.
+ */
+ private final ManifestDigest mManifestDigest;
+
+ /**
+ * Creates verification specifications for installing with application verification.
+ *
+ * @param verificationURI The location of the supplementary verification
+ * file. This can be a 'file:' or a 'content:' URI. May be {@code null}.
+ * @param originatingURI URI referencing where the package was downloaded
+ * from. May be {@code null}.
+ * @param referrer HTTP referrer URI associated with the originatingURI.
+ * May be {@code null}.
+ * @param originatingUid UID of the application that the install request originated
+ * from, or NO_UID if not present
+ * @param manifestDigest an object that holds the digest of the package
+ * which can be used to verify ownership. May be {@code null}.
+ */
+ public VerificationParams(Uri verificationURI, Uri originatingURI, Uri referrer,
+ int originatingUid, ManifestDigest manifestDigest) {
+ mVerificationURI = verificationURI;
+ mOriginatingURI = originatingURI;
+ mReferrer = referrer;
+ mOriginatingUid = originatingUid;
+ mManifestDigest = manifestDigest;
+ mInstallerUid = NO_UID;
+ }
+
+ public Uri getVerificationURI() {
+ return mVerificationURI;
+ }
+
+ public Uri getOriginatingURI() {
+ return mOriginatingURI;
+ }
+
+ public Uri getReferrer() {
+ return mReferrer;
+ }
+
+ /** return NO_UID if not available */
+ public int getOriginatingUid() {
+ return mOriginatingUid;
+ }
+
+ public ManifestDigest getManifestDigest() {
+ return mManifestDigest;
+ }
+
+ /** @return NO_UID when not set */
+ public int getInstallerUid() {
+ return mInstallerUid;
+ }
+
+ public void setInstallerUid(int uid) {
+ mInstallerUid = uid;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof VerificationParams)) {
+ return false;
+ }
+
+ final VerificationParams other = (VerificationParams) o;
+
+ if (mVerificationURI == null) {
+ if (other.mVerificationURI != null) {
+ return false;
+ }
+ } else if (!mVerificationURI.equals(other.mVerificationURI)) {
+ return false;
+ }
+
+ if (mOriginatingURI == null) {
+ if (other.mOriginatingURI != null) {
+ return false;
+ }
+ } else if (!mOriginatingURI.equals(other.mOriginatingURI)) {
+ return false;
+ }
+
+ if (mReferrer == null) {
+ if (other.mReferrer != null) {
+ return false;
+ }
+ } else if (!mReferrer.equals(other.mReferrer)) {
+ return false;
+ }
+
+ if (mOriginatingUid != other.mOriginatingUid) {
+ return false;
+ }
+
+ if (mManifestDigest == null) {
+ if (other.mManifestDigest != null) {
+ return false;
+ }
+ } else if (!mManifestDigest.equals(other.mManifestDigest)) {
+ return false;
+ }
+
+ if (mInstallerUid != other.mInstallerUid) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+
+ hash += 5 * (mVerificationURI == null ? 1 : mVerificationURI.hashCode());
+ hash += 7 * (mOriginatingURI == null ? 1 : mOriginatingURI.hashCode());
+ hash += 11 * (mReferrer == null ? 1 : mReferrer.hashCode());
+ hash += 13 * mOriginatingUid;
+ hash += 17 * (mManifestDigest == null ? 1 : mManifestDigest.hashCode());
+ hash += 19 * mInstallerUid;
+
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder(TO_STRING_PREFIX);
+
+ sb.append("mVerificationURI=");
+ sb.append(mVerificationURI.toString());
+ sb.append(",mOriginatingURI=");
+ sb.append(mOriginatingURI.toString());
+ sb.append(",mReferrer=");
+ sb.append(mReferrer.toString());
+ sb.append(",mOriginatingUid=");
+ sb.append(mOriginatingUid);
+ sb.append(",mManifestDigest=");
+ sb.append(mManifestDigest.toString());
+ sb.append(",mInstallerUid=");
+ sb.append(mInstallerUid);
+ sb.append('}');
+
+ return sb.toString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mVerificationURI, 0);
+ dest.writeParcelable(mOriginatingURI, 0);
+ dest.writeParcelable(mReferrer, 0);
+ dest.writeInt(mOriginatingUid);
+ dest.writeParcelable(mManifestDigest, 0);
+ dest.writeInt(mInstallerUid);
+ }
+
+
+ private VerificationParams(Parcel source) {
+ mVerificationURI = source.readParcelable(Uri.class.getClassLoader());
+ mOriginatingURI = source.readParcelable(Uri.class.getClassLoader());
+ mReferrer = source.readParcelable(Uri.class.getClassLoader());
+ mOriginatingUid = source.readInt();
+ mManifestDigest = source.readParcelable(ManifestDigest.class.getClassLoader());
+ mInstallerUid = source.readInt();
+ }
+
+ public static final Parcelable.Creator<VerificationParams> CREATOR =
+ new Parcelable.Creator<VerificationParams>() {
+ public VerificationParams createFromParcel(Parcel source) {
+ return new VerificationParams(source);
+ }
+
+ public VerificationParams[] newArray(int size) {
+ return new VerificationParams[size];
+ }
+ };
+}
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 1c9285e..28c751c 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -439,7 +439,7 @@ public class CompatibilityInfo implements Parcelable {
if (isScalingRequired()) {
float invertedRatio = applicationInvertedScale;
inoutDm.density = inoutDm.noncompatDensity * invertedRatio;
- inoutDm.densityDpi = (int)((inoutDm.density*DisplayMetrics.DENSITY_DEFAULT)+.5f);
+ inoutDm.densityDpi = (int)((inoutDm.noncompatDensityDpi * invertedRatio) + .5f);
inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio;
inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio;
inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio;
@@ -448,7 +448,7 @@ public class CompatibilityInfo implements Parcelable {
}
}
- public void applyToConfiguration(Configuration inoutConfig) {
+ public void applyToConfiguration(int displayDensity, Configuration inoutConfig) {
if (!supportsScreen()) {
// This is a larger screen device and the app is not
// compatible with large screens, so we are forcing it to
@@ -460,6 +460,11 @@ public class CompatibilityInfo implements Parcelable {
inoutConfig.screenHeightDp = inoutConfig.compatScreenHeightDp;
inoutConfig.smallestScreenWidthDp = inoutConfig.compatSmallestScreenWidthDp;
}
+ inoutConfig.densityDpi = displayDensity;
+ if (isScalingRequired()) {
+ float invertedRatio = applicationInvertedScale;
+ inoutConfig.densityDpi = (int)((inoutConfig.densityDpi * invertedRatio) + .5f);
+ }
}
/**
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 423b9af..86d6ee7 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -19,7 +19,7 @@ package android.content.res;
import android.content.pm.ActivityInfo;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.LocaleUtil;
+import android.text.TextUtils;
import android.view.View;
import java.util.Locale;
@@ -35,6 +35,9 @@ import java.util.Locale;
* <pre>Configuration config = getResources().getConfiguration();</pre>
*/
public final class Configuration implements Parcelable, Comparable<Configuration> {
+ /** @hide */
+ public static final Configuration EMPTY = new Configuration();
+
/**
* Current user preference for the scaling factor for fonts, relative
* to the base density scaling.
@@ -122,7 +125,25 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenAspectQualifier">long</a>
* resource qualifier. */
public static final int SCREENLAYOUT_LONG_YES = 0x20;
-
+
+ /** Constant for {@link #screenLayout}: bits that encode the layout direction. */
+ public static final int SCREENLAYOUT_LAYOUTDIR_MASK = 0xC0;
+ /** Constant for {@link #screenLayout}: bits shift to get the layout direction. */
+ public static final int SCREENLAYOUT_LAYOUTDIR_SHIFT = 6;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LAYOUTDIR_MASK}
+ * value indicating that no layout dir has been set. */
+ public static final int SCREENLAYOUT_LAYOUTDIR_UNDEFINED = 0x00;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LAYOUTDIR_MASK}
+ * value indicating that a layout dir has been set to LTR. */
+ public static final int SCREENLAYOUT_LAYOUTDIR_LTR = 0x01 << SCREENLAYOUT_LAYOUTDIR_SHIFT;
+ /** Constant for {@link #screenLayout}: a {@link #SCREENLAYOUT_LAYOUTDIR_MASK}
+ * value indicating that a layout dir has been set to RTL. */
+ public static final int SCREENLAYOUT_LAYOUTDIR_RTL = 0x02 << SCREENLAYOUT_LAYOUTDIR_SHIFT;
+
+ /** Constant for {@link #screenLayout}: a value indicating that screenLayout is undefined */
+ public static final int SCREENLAYOUT_UNDEFINED = SCREENLAYOUT_SIZE_UNDEFINED |
+ SCREENLAYOUT_LONG_UNDEFINED | SCREENLAYOUT_LAYOUTDIR_UNDEFINED;
+
/**
* Special flag we generate to indicate that the screen layout requires
* us to use a compatibility mode for apps that are not modern layout
@@ -143,11 +164,85 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* is wider/taller than normal. They may be one of
* {@link #SCREENLAYOUT_LONG_NO} or {@link #SCREENLAYOUT_LONG_YES}.
*
+ * <p>The {@link #SCREENLAYOUT_LAYOUTDIR_MASK} defines whether the screen layout
+ * is either LTR or RTL. They may be one of
+ * {@link #SCREENLAYOUT_LAYOUTDIR_LTR} or {@link #SCREENLAYOUT_LAYOUTDIR_RTL}.
+ *
* <p>See <a href="{@docRoot}guide/practices/screens_support.html">Supporting
* Multiple Screens</a> for more information.
*/
public int screenLayout;
-
+
+ /** @hide */
+ static public int resetScreenLayout(int curLayout) {
+ return (curLayout&~(SCREENLAYOUT_LONG_MASK | SCREENLAYOUT_SIZE_MASK
+ | SCREENLAYOUT_COMPAT_NEEDED))
+ | (SCREENLAYOUT_LONG_YES | SCREENLAYOUT_SIZE_XLARGE);
+ }
+
+ /** @hide */
+ static public int reduceScreenLayout(int curLayout, int longSizeDp, int shortSizeDp) {
+ int screenLayoutSize;
+ boolean screenLayoutLong;
+ boolean screenLayoutCompatNeeded;
+
+ // These semi-magic numbers define our compatibility modes for
+ // applications with different screens. These are guarantees to
+ // app developers about the space they can expect for a particular
+ // configuration. DO NOT CHANGE!
+ if (longSizeDp < 470) {
+ // This is shorter than an HVGA normal density screen (which
+ // is 480 pixels on its long side).
+ screenLayoutSize = SCREENLAYOUT_SIZE_SMALL;
+ screenLayoutLong = false;
+ screenLayoutCompatNeeded = false;
+ } else {
+ // What size is this screen screen?
+ if (longSizeDp >= 960 && shortSizeDp >= 720) {
+ // 1.5xVGA or larger screens at medium density are the point
+ // at which we consider it to be an extra large screen.
+ screenLayoutSize = SCREENLAYOUT_SIZE_XLARGE;
+ } else if (longSizeDp >= 640 && shortSizeDp >= 480) {
+ // VGA or larger screens at medium density are the point
+ // at which we consider it to be a large screen.
+ screenLayoutSize = SCREENLAYOUT_SIZE_LARGE;
+ } else {
+ screenLayoutSize = SCREENLAYOUT_SIZE_NORMAL;
+ }
+
+ // If this screen is wider than normal HVGA, or taller
+ // than FWVGA, then for old apps we want to run in size
+ // compatibility mode.
+ if (shortSizeDp > 321 || longSizeDp > 570) {
+ screenLayoutCompatNeeded = true;
+ } else {
+ screenLayoutCompatNeeded = false;
+ }
+
+ // Is this a long screen?
+ if (((longSizeDp*3)/5) >= (shortSizeDp-1)) {
+ // Anything wider than WVGA (5:3) is considering to be long.
+ screenLayoutLong = true;
+ } else {
+ screenLayoutLong = false;
+ }
+ }
+
+ // Now reduce the last screenLayout to not be better than what we
+ // have found.
+ if (!screenLayoutLong) {
+ curLayout = (curLayout&~SCREENLAYOUT_LONG_MASK) | SCREENLAYOUT_LONG_NO;
+ }
+ if (screenLayoutCompatNeeded) {
+ curLayout |= Configuration.SCREENLAYOUT_COMPAT_NEEDED;
+ }
+ int curSize = curLayout&SCREENLAYOUT_SIZE_MASK;
+ if (screenLayoutSize < curSize) {
+ curLayout = (curLayout&~SCREENLAYOUT_SIZE_MASK) | screenLayoutSize;
+ }
+ return curLayout;
+ }
+
/**
* Check if the Configuration's current {@link #screenLayout} is at
* least the given size.
@@ -369,26 +464,40 @@ public final class Configuration implements Parcelable, Comparable<Configuration
*/
public int uiMode;
+ /**
+ * Default value for {@link #screenWidthDp} indicating that no width
+ * has been specified.
+ */
public static final int SCREEN_WIDTH_DP_UNDEFINED = 0;
/**
* The current width of the available screen space, in dp units,
* corresponding to
* <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenWidthQualifier">screen
- * width</a> resource qualifier.
+ * width</a> resource qualifier. Set to
+ * {@link #SCREEN_WIDTH_DP_UNDEFINED} if no width is specified.
*/
public int screenWidthDp;
+ /**
+ * Default value for {@link #screenHeightDp} indicating that no width
+ * has been specified.
+ */
public static final int SCREEN_HEIGHT_DP_UNDEFINED = 0;
/**
* The current height of the available screen space, in dp units,
* corresponding to
* <a href="{@docRoot}guide/topics/resources/providing-resources.html#ScreenHeightQualifier">screen
- * height</a> resource qualifier.
+ * height</a> resource qualifier. Set to
+ * {@link #SCREEN_HEIGHT_DP_UNDEFINED} if no height is specified.
*/
public int screenHeightDp;
+ /**
+ * Default value for {@link #smallestScreenWidthDp} indicating that no width
+ * has been specified.
+ */
public static final int SMALLEST_SCREEN_WIDTH_DP_UNDEFINED = 0;
/**
@@ -397,10 +506,26 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* <a href="{@docRoot}guide/topics/resources/providing-resources.html#SmallestScreenWidthQualifier">smallest
* screen width</a> resource qualifier.
* This is the smallest value of both screenWidthDp and screenHeightDp
- * in both portrait and landscape.
+ * in both portrait and landscape. Set to
+ * {@link #SMALLEST_SCREEN_WIDTH_DP_UNDEFINED} if no width is specified.
*/
public int smallestScreenWidthDp;
+ /**
+ * Default value for {@link #densityDpi} indicating that no width
+ * has been specified.
+ */
+ public static final int DENSITY_DPI_UNDEFINED = 0;
+
+ /**
+ * The target screen density being rendered to,
+ * corresponding to
+ * <a href="{@docRoot}guide/topics/resources/providing-resources.html#DensityQualifier">density</a>
+ * resource qualifier. Set to
+ * {@link #DENSITY_DPI_UNDEFINED} if no density is specified.
+ */
+ public int densityDpi;
+
/** @hide Hack to get this information from WM to app running in compat mode. */
public int compatScreenWidthDp;
/** @hide Hack to get this information from WM to app running in compat mode. */
@@ -409,11 +534,6 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public int compatSmallestScreenWidthDp;
/**
- * @hide The layout direction associated to the current Locale
- */
- public int layoutDirection;
-
- /**
* @hide Internal book-keeping.
*/
public int seq;
@@ -439,7 +559,6 @@ public final class Configuration implements Parcelable, Comparable<Configuration
mnc = o.mnc;
if (o.locale != null) {
locale = (Locale) o.locale.clone();
- layoutDirection = o.layoutDirection;
}
userSetLocale = o.userSetLocale;
touchscreen = o.touchscreen;
@@ -454,6 +573,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
screenWidthDp = o.screenWidthDp;
screenHeightDp = o.screenHeightDp;
smallestScreenWidthDp = o.smallestScreenWidthDp;
+ densityDpi = o.densityDpi;
compatScreenWidthDp = o.compatScreenWidthDp;
compatScreenHeightDp = o.compatScreenHeightDp;
compatSmallestScreenWidthDp = o.compatSmallestScreenWidthDp;
@@ -465,20 +585,31 @@ public final class Configuration implements Parcelable, Comparable<Configuration
sb.append("{");
sb.append(fontScale);
sb.append(" ");
- sb.append(mcc);
- sb.append("mcc");
- sb.append(mnc);
- sb.append("mnc");
+ if (mcc != 0) {
+ sb.append(mcc);
+ sb.append("mcc");
+ } else {
+ sb.append("?mcc");
+ }
+ if (mnc != 0) {
+ sb.append(mnc);
+ sb.append("mnc");
+ } else {
+ sb.append("?mnc");
+ }
if (locale != null) {
sb.append(" ");
sb.append(locale);
} else {
- sb.append(" (no locale)");
+ sb.append(" ?locale");
}
- switch (layoutDirection) {
- case View.LAYOUT_DIRECTION_LTR: /* ltr not interesting */ break;
- case View.LAYOUT_DIRECTION_RTL: sb.append(" rtl"); break;
- default: sb.append(" layoutDir="); sb.append(layoutDirection); break;
+ int layoutDir = (screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK);
+ switch (layoutDir) {
+ case SCREENLAYOUT_LAYOUTDIR_UNDEFINED: sb.append(" ?layoutDir"); break;
+ case SCREENLAYOUT_LAYOUTDIR_LTR: sb.append(" ldltr"); break;
+ case SCREENLAYOUT_LAYOUTDIR_RTL: sb.append(" ldrtl"); break;
+ default: sb.append(" layoutDir=");
+ sb.append(layoutDir >> SCREENLAYOUT_LAYOUTDIR_SHIFT); break;
}
if (smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
sb.append(" sw"); sb.append(smallestScreenWidthDp); sb.append("dp");
@@ -495,6 +626,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
} else {
sb.append(" ?hdp");
}
+ if (densityDpi != DENSITY_DPI_UNDEFINED) {
+ sb.append(" "); sb.append(densityDpi); sb.append("dpi");
+ } else {
+ sb.append(" ?density");
+ }
switch ((screenLayout&SCREENLAYOUT_SIZE_MASK)) {
case SCREENLAYOUT_SIZE_UNDEFINED: sb.append(" ?lsize"); break;
case SCREENLAYOUT_SIZE_SMALL: sb.append(" smll"); break;
@@ -596,12 +732,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
navigation = NAVIGATION_UNDEFINED;
navigationHidden = NAVIGATIONHIDDEN_UNDEFINED;
orientation = ORIENTATION_UNDEFINED;
- screenLayout = SCREENLAYOUT_SIZE_UNDEFINED;
+ screenLayout = SCREENLAYOUT_UNDEFINED;
uiMode = UI_MODE_TYPE_UNDEFINED;
screenWidthDp = compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
screenHeightDp = compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
- layoutDirection = View.LAYOUT_DIRECTION_LTR;
+ densityDpi = DENSITY_DPI_UNDEFINED;
seq = 0;
}
@@ -637,7 +773,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
changed |= ActivityInfo.CONFIG_LOCALE;
locale = delta.locale != null
? (Locale) delta.locale.clone() : null;
- layoutDirection = LocaleUtil.getLayoutDirectionFromLocale(locale);
+ // If locale has changed, then layout direction is also changed ...
+ changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
+ // ... and we need to update the layout direction (represented by the first
+ // 2 most significant bits in screenLayout).
+ setLayoutDirection(locale);
}
if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))
{
@@ -679,10 +819,17 @@ public final class Configuration implements Parcelable, Comparable<Configuration
changed |= ActivityInfo.CONFIG_ORIENTATION;
orientation = delta.orientation;
}
- if (delta.screenLayout != SCREENLAYOUT_SIZE_UNDEFINED
- && screenLayout != delta.screenLayout) {
+ if (getScreenLayoutNoDirection(delta.screenLayout) !=
+ (SCREENLAYOUT_SIZE_UNDEFINED | SCREENLAYOUT_LONG_UNDEFINED)
+ && (getScreenLayoutNoDirection(screenLayout) !=
+ getScreenLayoutNoDirection(delta.screenLayout))) {
changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
- screenLayout = delta.screenLayout;
+ // We need to preserve the previous layout dir bits if they were defined
+ if ((delta.screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK) == 0) {
+ screenLayout = (screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK)|delta.screenLayout;
+ } else {
+ screenLayout = delta.screenLayout;
+ }
}
if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED)
&& uiMode != delta.uiMode) {
@@ -707,8 +854,13 @@ public final class Configuration implements Parcelable, Comparable<Configuration
screenHeightDp = delta.screenHeightDp;
}
if (delta.smallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
+ changed |= ActivityInfo.CONFIG_SCREEN_SIZE;
smallestScreenWidthDp = delta.smallestScreenWidthDp;
}
+ if (delta.densityDpi != DENSITY_DPI_UNDEFINED) {
+ changed |= ActivityInfo.CONFIG_DENSITY;
+ densityDpi = delta.densityDpi;
+ }
if (delta.compatScreenWidthDp != SCREEN_WIDTH_DP_UNDEFINED) {
compatScreenWidthDp = delta.compatScreenWidthDp;
}
@@ -718,7 +870,6 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (delta.compatSmallestScreenWidthDp != SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
compatSmallestScreenWidthDp = delta.compatSmallestScreenWidthDp;
}
-
if (delta.seq != 0) {
seq = delta.seq;
}
@@ -754,6 +905,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* PackageManager.ActivityInfo.CONFIG_SCREEN_SIZE}, or
* {@link android.content.pm.ActivityInfo#CONFIG_SMALLEST_SCREEN_SIZE
* PackageManager.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE}.
+ * {@link android.content.pm.ActivityInfo#CONFIG_LAYOUT_DIRECTION
+ * PackageManager.ActivityInfo.CONFIG_LAYOUT_DIRECTION}.
*/
public int diff(Configuration delta) {
int changed = 0;
@@ -769,6 +922,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (delta.locale != null
&& (locale == null || !locale.equals(delta.locale))) {
changed |= ActivityInfo.CONFIG_LOCALE;
+ changed |= ActivityInfo.CONFIG_LAYOUT_DIRECTION;
}
if (delta.touchscreen != TOUCHSCREEN_UNDEFINED
&& touchscreen != delta.touchscreen) {
@@ -798,8 +952,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
&& orientation != delta.orientation) {
changed |= ActivityInfo.CONFIG_ORIENTATION;
}
- if (delta.screenLayout != SCREENLAYOUT_SIZE_UNDEFINED
- && screenLayout != delta.screenLayout) {
+ if (getScreenLayoutNoDirection(delta.screenLayout) !=
+ (SCREENLAYOUT_SIZE_UNDEFINED | SCREENLAYOUT_LONG_UNDEFINED)
+ && getScreenLayoutNoDirection(screenLayout) !=
+ getScreenLayoutNoDirection(delta.screenLayout)) {
changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
}
if (delta.uiMode != (UI_MODE_TYPE_UNDEFINED|UI_MODE_NIGHT_UNDEFINED)
@@ -818,7 +974,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
&& smallestScreenWidthDp != delta.smallestScreenWidthDp) {
changed |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
}
-
+ if (delta.densityDpi != DENSITY_DPI_UNDEFINED
+ && densityDpi != delta.densityDpi) {
+ changed |= ActivityInfo.CONFIG_DENSITY;
+ }
+
return changed;
}
@@ -902,10 +1062,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
dest.writeInt(screenWidthDp);
dest.writeInt(screenHeightDp);
dest.writeInt(smallestScreenWidthDp);
+ dest.writeInt(densityDpi);
dest.writeInt(compatScreenWidthDp);
dest.writeInt(compatScreenHeightDp);
dest.writeInt(compatSmallestScreenWidthDp);
- dest.writeInt(layoutDirection);
dest.writeInt(seq);
}
@@ -930,10 +1090,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
screenWidthDp = source.readInt();
screenHeightDp = source.readInt();
smallestScreenWidthDp = source.readInt();
+ densityDpi = source.readInt();
compatScreenWidthDp = source.readInt();
compatScreenHeightDp = source.readInt();
compatSmallestScreenWidthDp = source.readInt();
- layoutDirection = source.readInt();
seq = source.readInt();
}
@@ -1000,6 +1160,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
n = this.screenHeightDp - that.screenHeightDp;
if (n != 0) return n;
n = this.smallestScreenWidthDp - that.smallestScreenWidthDp;
+ if (n != 0) return n;
+ n = this.densityDpi - that.densityDpi;
//if (n != 0) return n;
return n;
}
@@ -1036,6 +1198,53 @@ public final class Configuration implements Parcelable, Comparable<Configuration
result = 31 * result + screenWidthDp;
result = 31 * result + screenHeightDp;
result = 31 * result + smallestScreenWidthDp;
+ result = 31 * result + densityDpi;
return result;
}
+
+ /**
+ * Set the locale. This is the preferred way for setting up the locale (instead of using the
+ * direct accessor). This will also set the userLocale and layout direction according to
+ * the locale.
+ *
+ * @param loc The locale. Can be null.
+ */
+ public void setLocale(Locale loc) {
+ locale = loc;
+ userSetLocale = true;
+ setLayoutDirection(locale);
+ }
+
+ /**
+ * Return the layout direction. Will be either {@link View#LAYOUT_DIRECTION_LTR} or
+ * {@link View#LAYOUT_DIRECTION_RTL}.
+ *
+ * @return the layout direction
+ */
+ public int getLayoutDirection() {
+ // We need to substract one here as the configuration values are using "0" as undefined thus
+ // having LRT set to "1" and RTL set to "2"
+ return ((screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK) >> SCREENLAYOUT_LAYOUTDIR_SHIFT) - 1;
+ }
+
+ /**
+ * Set the layout direction from the Locale.
+ *
+ * @param locale The Locale. If null will set the layout direction to
+ * {@link View#LAYOUT_DIRECTION_LTR}. If not null will set it to the layout direction
+ * corresponding to the Locale.
+ *
+ * @see {@link View#LAYOUT_DIRECTION_LTR} and {@link View#LAYOUT_DIRECTION_RTL}
+ */
+ public void setLayoutDirection(Locale locale) {
+ // There is a "1" difference between the configuration values for
+ // layout direction and View constants for layout direction, just add "1".
+ final int layoutDirection = 1 + TextUtils.getLayoutDirectionFromLocale(locale);
+ screenLayout = (screenLayout&~SCREENLAYOUT_LAYOUTDIR_MASK)|
+ (layoutDirection << SCREENLAYOUT_LAYOUTDIR_SHIFT);
+ }
+
+ private static int getScreenLayoutNoDirection(int screenLayout) {
+ return screenLayout&~SCREENLAYOUT_LAYOUTDIR_MASK;
+ }
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index ab2fe1c..b316f23 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -89,7 +89,8 @@ public class Resources {
= new LongSparseArray<ColorStateList>();
private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
= new LongSparseArray<Drawable.ConstantState>();
- private static boolean mPreloaded;
+ private static boolean sPreloaded;
+ private static int sPreloadedDensity;
/*package*/ final TypedValue mTmpValue = new TypedValue();
/*package*/ final Configuration mTmpConfig = new Configuration();
@@ -693,9 +694,9 @@ public class Resources {
*/
if (value.density > 0 && value.density != TypedValue.DENSITY_NONE) {
if (value.density == density) {
- value.density = DisplayMetrics.DENSITY_DEVICE;
+ value.density = mMetrics.densityDpi;
} else {
- value.density = (value.density * DisplayMetrics.DENSITY_DEVICE) / density;
+ value.density = (value.density * mMetrics.densityDpi) / density;
}
}
@@ -1434,17 +1435,27 @@ public class Resources {
int configChanges = 0xfffffff;
if (config != null) {
mTmpConfig.setTo(config);
+ int density = config.densityDpi;
+ if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+ density = mMetrics.noncompatDensityDpi;
+ }
if (mCompatibilityInfo != null) {
- mCompatibilityInfo.applyToConfiguration(mTmpConfig);
+ mCompatibilityInfo.applyToConfiguration(density, mTmpConfig);
}
if (mTmpConfig.locale == null) {
mTmpConfig.locale = Locale.getDefault();
+ mTmpConfig.setLayoutDirection(mTmpConfig.locale);
}
configChanges = mConfiguration.updateFrom(mTmpConfig);
configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
}
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
+ mConfiguration.setLayoutDirection(mConfiguration.locale);
+ }
+ if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
+ mMetrics.densityDpi = mConfiguration.densityDpi;
+ mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
}
mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
@@ -1474,7 +1485,7 @@ public class Resources {
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
locale, mConfiguration.orientation,
mConfiguration.touchscreen,
- (int)(mMetrics.density*160), mConfiguration.keyboard,
+ mConfiguration.densityDpi, mConfiguration.keyboard,
keyboardHidden, mConfiguration.navigation, width, height,
mConfiguration.smallestScreenWidthDp,
mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
@@ -1836,11 +1847,14 @@ public class Resources {
*/
public final void startPreloading() {
synchronized (mSync) {
- if (mPreloaded) {
+ if (sPreloaded) {
throw new IllegalStateException("Resources already preloaded");
}
- mPreloaded = true;
+ sPreloaded = true;
mPreloading = true;
+ sPreloadedDensity = DisplayMetrics.DENSITY_DEVICE;
+ mConfiguration.densityDpi = sPreloadedDensity;
+ updateConfiguration(null, null);
}
}
@@ -1854,7 +1868,24 @@ public class Resources {
flushLayoutCache();
}
}
-
+
+ private boolean verifyPreloadConfig(TypedValue value, String name) {
+ if ((value.changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE
+ | ActivityInfo.CONFIG_DENSITY)) != 0) {
+ String resName;
+ try {
+ resName = getResourceName(value.resourceId);
+ } catch (NotFoundException e) {
+ resName = "?";
+ }
+ Log.w(TAG, "Preloaded " + name + " resource #0x"
+ + Integer.toHexString(value.resourceId)
+ + " (" + resName + ") that varies with configuration!!");
+ return false;
+ }
+ return true;
+ }
+
/*package*/ Drawable loadDrawable(TypedValue value, int id)
throws NotFoundException {
@@ -1866,20 +1897,24 @@ public class Resources {
}
}
- final long key = (((long) value.assetCookie) << 32) | value.data;
boolean isColorDrawable = false;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
isColorDrawable = true;
}
+ final long key = isColorDrawable ? value.data :
+ (((long) value.assetCookie) << 32) | value.data;
+
Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key);
if (dr != null) {
return dr;
}
- Drawable.ConstantState cs = isColorDrawable ?
- sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key);
+ Drawable.ConstantState cs = isColorDrawable
+ ? sPreloadedColorDrawables.get(key)
+ : (sPreloadedDensity == mConfiguration.densityDpi
+ ? sPreloadedDrawables.get(key) : null);
if (cs != null) {
dr = cs.newDrawable(this);
} else {
@@ -1947,10 +1982,12 @@ public class Resources {
cs = dr.getConstantState();
if (cs != null) {
if (mPreloading) {
- if (isColorDrawable) {
- sPreloadedColorDrawables.put(key, cs);
- } else {
- sPreloadedDrawables.put(key, cs);
+ if (verifyPreloadConfig(value, "drawable")) {
+ if (isColorDrawable) {
+ sPreloadedColorDrawables.put(key, cs);
+ } else {
+ sPreloadedDrawables.put(key, cs);
+ }
}
} else {
synchronized (mTmpValue) {
@@ -2015,7 +2052,9 @@ public class Resources {
csl = ColorStateList.valueOf(value.data);
if (mPreloading) {
- sPreloadedColorStateLists.put(key, csl);
+ if (verifyPreloadConfig(value, "color")) {
+ sPreloadedColorStateLists.put(key, csl);
+ }
}
return csl;
@@ -2059,7 +2098,9 @@ public class Resources {
if (csl != null) {
if (mPreloading) {
- sPreloadedColorStateLists.put(key, csl);
+ if (verifyPreloadConfig(value, "color")) {
+ sPreloadedColorStateLists.put(key, csl);
+ }
} else {
synchronized (mTmpValue) {
//Log.i(TAG, "Saving cached color state list @ #" +
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 2968fbb..7f3b6b9 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -469,13 +469,20 @@ public class TypedArray {
* {@link android.view.ViewGroup}'s layout_width and layout_height
* attributes. This is only here for performance reasons; applications
* should use {@link #getDimensionPixelSize}.
- *
+ *
* @param index Index of the attribute to retrieve.
* @param name Textual name of attribute for error reporting.
*
* @return Attribute dimension value multiplied by the appropriate
* metric and truncated to integer pixels.
+ *
+ * @throws RuntimeException
+ * if this TypedArray does not contain an entry for <code>index</code>
+ *
+ * @deprecated Use {@link #getLayoutDimension(int, int)} instead.
+ *
*/
+ @Deprecated
public int getLayoutDimension(int index, String name) {
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index a6af5c2..1fc1226 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -50,9 +50,6 @@ public class DatabaseUtils {
private static final String TAG = "DatabaseUtils";
private static final boolean DEBUG = false;
- private static final boolean LOCAL_LOGV = false;
-
- private static final String[] countProjection = new String[]{"count(*)"};
/** One of the values returned by {@link #getSqlStatementType(String)}. */
public static final int STATEMENT_SELECT = 1;
@@ -963,10 +960,15 @@ public class DatabaseUtils {
}
/**
- * This class allows users to do multiple inserts into a table but
- * compile the SQL insert statement only once, which may increase
- * performance.
+ * This class allows users to do multiple inserts into a table using
+ * the same statement.
+ * <p>
+ * This class is not thread-safe.
+ * </p>
+ *
+ * @deprecated Use {@link SQLiteStatement} instead.
*/
+ @Deprecated
public static class InsertHelper {
private final SQLiteDatabase mDb;
private final String mTableName;
@@ -983,6 +985,13 @@ public class DatabaseUtils {
* table_info(...)" command that we depend on.
*/
public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1;
+
+ /**
+ * This field was accidentally exposed in earlier versions of the platform
+ * so we can hide it but we can't remove it.
+ *
+ * @hide
+ */
public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4;
/**
@@ -1036,7 +1045,7 @@ public class DatabaseUtils {
sb.append(sbv);
mInsertSQL = sb.toString();
- if (LOCAL_LOGV) Log.v(TAG, "insert statement is " + mInsertSQL);
+ if (DEBUG) Log.v(TAG, "insert statement is " + mInsertSQL);
}
private SQLiteStatement getStatement(boolean allowReplace) throws SQLException {
@@ -1069,24 +1078,35 @@ public class DatabaseUtils {
* @return the row ID of the newly inserted row, or -1 if an
* error occurred
*/
- private synchronized long insertInternal(ContentValues values, boolean allowReplace) {
+ private long insertInternal(ContentValues values, boolean allowReplace) {
+ // Start a transaction even though we don't really need one.
+ // This is to help maintain compatibility with applications that
+ // access InsertHelper from multiple threads even though they never should have.
+ // The original code used to lock the InsertHelper itself which was prone
+ // to deadlocks. Starting a transaction achieves the same mutual exclusion
+ // effect as grabbing a lock but without the potential for deadlocks.
+ mDb.beginTransactionNonExclusive();
try {
SQLiteStatement stmt = getStatement(allowReplace);
stmt.clearBindings();
- if (LOCAL_LOGV) Log.v(TAG, "--- inserting in table " + mTableName);
+ if (DEBUG) Log.v(TAG, "--- inserting in table " + mTableName);
for (Map.Entry<String, Object> e: values.valueSet()) {
final String key = e.getKey();
int i = getColumnIndex(key);
DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue());
- if (LOCAL_LOGV) {
+ if (DEBUG) {
Log.v(TAG, "binding " + e.getValue() + " to column " +
i + " (" + key + ")");
}
}
- return stmt.executeInsert();
+ long result = stmt.executeInsert();
+ mDb.setTransactionSuccessful();
+ return result;
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values + " into table " + mTableName, e);
return -1;
+ } finally {
+ mDb.endTransaction();
}
}
@@ -1223,7 +1243,7 @@ public class DatabaseUtils {
+ "execute");
}
try {
- if (LOCAL_LOGV) Log.v(TAG, "--- doing insert or replace in table " + mTableName);
+ if (DEBUG) Log.v(TAG, "--- doing insert or replace in table " + mTableName);
return mPreparedStatement.executeInsert();
} catch (SQLException e) {
Log.e(TAG, "Error executing InsertHelper with table " + mTableName, e);
diff --git a/core/java/android/ddm/DdmHandleAppName.java b/core/java/android/ddm/DdmHandleAppName.java
index 78dd23e..7e39e47 100644
--- a/core/java/android/ddm/DdmHandleAppName.java
+++ b/core/java/android/ddm/DdmHandleAppName.java
@@ -69,14 +69,14 @@ public class DdmHandleAppName extends ChunkHandler {
* before or after DDMS connects. For the latter we need to send up
* an APNM message.
*/
- public static void setAppName(String name) {
+ public static void setAppName(String name, int userId) {
if (name == null || name.length() == 0)
return;
mAppName = name;
// if DDMS is already connected, send the app name up
- sendAPNM(name);
+ sendAPNM(name, userId);
}
public static String getAppName() {
@@ -86,14 +86,18 @@ public class DdmHandleAppName extends ChunkHandler {
/*
* Send an APNM (APplication NaMe) chunk.
*/
- private static void sendAPNM(String appName) {
+ private static void sendAPNM(String appName, int userId) {
if (false)
Log.v("ddm", "Sending app name");
- ByteBuffer out = ByteBuffer.allocate(4 + appName.length()*2);
+ ByteBuffer out = ByteBuffer.allocate(
+ 4 /* appName's length */
+ + appName.length()*2 /* appName */
+ + 4 /* userId */);
out.order(ChunkHandler.CHUNK_ORDER);
out.putInt(appName.length());
putString(out, appName);
+ out.putInt(userId);
Chunk chunk = new Chunk(CHUNK_APNM, out);
DdmServer.sendChunk(chunk);
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index 5088d22..e99fa92 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -21,6 +21,7 @@ import org.apache.harmony.dalvik.ddmc.ChunkHandler;
import org.apache.harmony.dalvik.ddmc.DdmServer;
import android.util.Log;
import android.os.Debug;
+import android.os.UserHandle;
import java.nio.ByteBuffer;
@@ -119,7 +120,7 @@ public class DdmHandleHello extends ChunkHandler {
// appName = "unknown";
String appName = DdmHandleAppName.getAppName();
- ByteBuffer out = ByteBuffer.allocate(16
+ ByteBuffer out = ByteBuffer.allocate(20
+ vmIdent.length()*2 + appName.length()*2);
out.order(ChunkHandler.CHUNK_ORDER);
out.putInt(DdmServer.CLIENT_PROTOCOL_VERSION);
@@ -128,6 +129,7 @@ public class DdmHandleHello extends ChunkHandler {
out.putInt(appName.length());
putString(out, vmIdent);
putString(out, appName);
+ out.putInt(UserHandle.myUserId());
Chunk reply = new Chunk(CHUNK_HELO, out);
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 829620b..1e8671b 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -233,6 +233,21 @@ public class Camera {
* @see Parameters#setJpegThumbnailSize(int, int)
*/
public int orientation;
+
+ /**
+ * <p>Whether the shutter sound can be disabled.</p>
+ *
+ * <p>On some devices, the camera shutter sound cannot be turned off
+ * through {@link #enableShutterSound enableShutterSound}. This field
+ * can be used to determine whether a call to disable the shutter sound
+ * will succeed.</p>
+ *
+ * <p>If this field is set to true, then a call of
+ * {@code enableShutterSound(false)} will be successful. If set to
+ * false, then that call will fail, and the shutter sound will be played
+ * when {@link Camera#takePicture takePicture} is called.</p>
+ */
+ public boolean canDisableShutterSound;
};
/**
@@ -1146,6 +1161,33 @@ public class Camera {
public native final void setDisplayOrientation(int degrees);
/**
+ * <p>Enable or disable the default shutter sound when taking a picture.</p>
+ *
+ * <p>By default, the camera plays the system-defined camera shutter sound
+ * when {@link #takePicture} is called. Using this method, the shutter sound
+ * can be disabled. It is strongly recommended that an alternative shutter
+ * sound is played in the {@link ShutterCallback} when the system shutter
+ * sound is disabled.</p>
+ *
+ * <p>Note that devices may not always allow disabling the camera shutter
+ * sound. If the shutter sound state cannot be set to the desired value,
+ * this method will return false. {@link CameraInfo#canDisableShutterSound}
+ * can be used to determine whether the device will allow the shutter sound
+ * to be disabled.</p>
+ *
+ * @param enabled whether the camera should play the system shutter sound
+ * when {@link #takePicture takePicture} is called.
+ * @return {@code true} if the shutter sound state was successfully
+ * changed. {@code false} if the shutter sound state could not be
+ * changed. {@code true} is also returned if shutter sound playback
+ * is already set to the requested state.
+ * @see #takePicture
+ * @see CameraInfo#canDisableShutterSound
+ * @see ShutterCallback
+ */
+ public native final boolean enableShutterSound(boolean enabled);
+
+ /**
* Callback interface for zoom changes during a smooth zoom operation.
*
* @see #setZoomChangeListener(OnZoomChangeListener)
@@ -1308,8 +1350,14 @@ public class Camera {
public Rect rect;
/**
- * The confidence level for the detection of the face. The range is 1 to 100. 100 is the
- * highest confidence.
+ * <p>The confidence level for the detection of the face. The range is 1 to
+ * 100. 100 is the highest confidence.</p>
+ *
+ * <p>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 50 is recommended.</p>
*
* @see #startFaceDetection()
*/
@@ -1782,6 +1830,14 @@ public class Camera {
public static final String SCENE_MODE_BARCODE = "barcode";
/**
+ * Capture a scene using high dynamic range imaging techniques. The
+ * camera will return an image that has an extended dynamic range
+ * compared to a regular capture. Capturing such an image may take
+ * longer than a regular capture.
+ */
+ public static final String SCENE_MODE_HDR = "hdr";
+
+ /**
* Auto-focus mode. Applications should call {@link
* #autoFocus(AutoFocusCallback)} to start the focus in this mode.
*/
@@ -2784,6 +2840,7 @@ public class Camera {
* @see #SCENE_MODE_SPORTS
* @see #SCENE_MODE_PARTY
* @see #SCENE_MODE_CANDLELIGHT
+ * @see #SCENE_MODE_BARCODE
*/
public String getSceneMode() {
return get(KEY_SCENE_MODE);
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 3c70dc6..e0c9d2c 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -26,7 +26,7 @@ package android.hardware;
* @see SensorEvent
*
*/
-public class Sensor {
+public final class Sensor {
/**
* A constant describing an accelerometer sensor type. See
@@ -202,4 +202,11 @@ public class Sensor {
mMaxRange = max;
mResolution = res;
}
+
+ @Override
+ public String toString() {
+ return "{Sensor name=\"" + mName + "\", vendor=\"" + mVendor + "\", version=" + mVersion
+ + ", type=" + mType + ", maxRange=" + mMaxRange + ", resolution=" + mResolution
+ + ", power=" + mPower + ", minDelay=" + mMinDelay + "}";
+ }
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
new file mode 100644
index 0000000..28e320b
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -0,0 +1,258 @@
+/*
+ * 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.content.Context;
+import android.os.Handler;
+import android.util.SparseArray;
+import android.view.Display;
+
+/**
+ * Manages the properties of attached displays.
+ * <p>
+ * Get an instance of this class by calling
+ * {@link android.content.Context#getSystemService(java.lang.String)
+ * Context.getSystemService()} with the argument
+ * {@link android.content.Context#DISPLAY_SERVICE}.
+ * </p>
+ */
+public final class DisplayManager {
+ private static final String TAG = "DisplayManager";
+ private static final boolean DEBUG = false;
+
+ private final Context mContext;
+ private final DisplayManagerGlobal mGlobal;
+
+ private final Object mLock = new Object();
+ private final SparseArray<Display> mDisplays = new SparseArray<Display>();
+
+ /**
+ * Broadcast receiver that indicates when the Wifi display status changes.
+ * <p>
+ * The status is provided as a {@link WifiDisplayStatus} object in the
+ * {@link #EXTRA_WIFI_DISPLAY_STATUS} extra.
+ * </p><p>
+ * This broadcast is only sent to registered receivers and can only be sent by the system.
+ * </p>
+ * @hide
+ */
+ public static final String ACTION_WIFI_DISPLAY_STATUS_CHANGED =
+ "android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED";
+
+ /**
+ * Contains a {@link WifiDisplayStatus} object.
+ * @hide
+ */
+ public static final String EXTRA_WIFI_DISPLAY_STATUS =
+ "android.hardware.display.extra.WIFI_DISPLAY_STATUS";
+
+ /** @hide */
+ public DisplayManager(Context context) {
+ mContext = context;
+ mGlobal = DisplayManagerGlobal.getInstance();
+ }
+
+ /**
+ * Gets information about a logical display.
+ *
+ * The display metrics may be adjusted to provide compatibility
+ * for legacy applications.
+ *
+ * @param displayId The logical display id.
+ * @return The display object, or null if there is no valid display with the given id.
+ */
+ public Display getDisplay(int displayId) {
+ synchronized (mLock) {
+ return getOrCreateDisplayLocked(displayId, false /*assumeValid*/);
+ }
+ }
+
+ /**
+ * Gets all currently valid logical displays.
+ *
+ * @return An array containing all displays.
+ */
+ public Display[] getDisplays() {
+ int[] displayIds = mGlobal.getDisplayIds();
+ int expectedCount = displayIds.length;
+ Display[] displays = new Display[expectedCount];
+ synchronized (mLock) {
+ int actualCount = 0;
+ for (int i = 0; i < expectedCount; i++) {
+ Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/);
+ if (display != null) {
+ displays[actualCount++] = display;
+ }
+ }
+ if (actualCount != expectedCount) {
+ Display[] oldDisplays = displays;
+ displays = new Display[actualCount];
+ System.arraycopy(oldDisplays, 0, displays, 0, actualCount);
+ }
+ }
+ return displays;
+ }
+
+ private Display getOrCreateDisplayLocked(int displayId, boolean assumeValid) {
+ Display display = mDisplays.get(displayId);
+ if (display == null) {
+ display = mGlobal.getCompatibleDisplay(displayId,
+ mContext.getCompatibilityInfo(displayId));
+ if (display != null) {
+ mDisplays.put(displayId, display);
+ }
+ } else if (!assumeValid && !display.isValid()) {
+ display = null;
+ }
+ return display;
+ }
+
+ /**
+ * Registers an display listener to receive notifications about when
+ * displays are added, removed or changed.
+ *
+ * @param listener The listener to register.
+ * @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.
+ *
+ * @see #unregisterDisplayListener
+ */
+ public void registerDisplayListener(DisplayListener listener, Handler handler) {
+ mGlobal.registerDisplayListener(listener, handler);
+ }
+
+ /**
+ * Unregisters an input device listener.
+ *
+ * @param listener The listener to unregister.
+ *
+ * @see #registerDisplayListener
+ */
+ public void unregisterDisplayListener(DisplayListener listener) {
+ mGlobal.unregisterDisplayListener(listener);
+ }
+
+ /**
+ * Initiates a fresh scan of availble Wifi displays.
+ * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast.
+ * @hide
+ */
+ public void scanWifiDisplays() {
+ mGlobal.scanWifiDisplays();
+ }
+
+ /**
+ * Connects to a Wifi display.
+ * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast.
+ * <p>
+ * Automatically remembers the display after a successful connection, if not
+ * already remembered.
+ * </p><p>
+ * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY} to connect
+ * to unknown displays. No permissions are required to connect to already known displays.
+ * </p>
+ *
+ * @param deviceAddress The MAC address of the device to which we should connect.
+ * @hide
+ */
+ public void connectWifiDisplay(String deviceAddress) {
+ mGlobal.connectWifiDisplay(deviceAddress);
+ }
+
+ /**
+ * Disconnects from the current Wifi display.
+ * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast.
+ * @hide
+ */
+ public void disconnectWifiDisplay() {
+ mGlobal.disconnectWifiDisplay();
+ }
+
+ /**
+ * Renames a Wifi display.
+ * <p>
+ * The display must already be remembered for this call to succeed. In other words,
+ * we must already have successfully connected to the display at least once and then
+ * not forgotten it.
+ * </p><p>
+ * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}.
+ * </p>
+ *
+ * @param deviceAddress The MAC address of the device to rename.
+ * @param alias The alias name by which to remember the device, or null
+ * or empty if no alias should be used.
+ * @hide
+ */
+ public void renameWifiDisplay(String deviceAddress, String alias) {
+ mGlobal.renameWifiDisplay(deviceAddress, alias);
+ }
+
+ /**
+ * Forgets a previously remembered Wifi display.
+ * <p>
+ * Automatically disconnects from the display if currently connected to it.
+ * </p><p>
+ * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}.
+ * </p>
+ *
+ * @param deviceAddress The MAC address of the device to forget.
+ * @hide
+ */
+ public void forgetWifiDisplay(String deviceAddress) {
+ mGlobal.forgetWifiDisplay(deviceAddress);
+ }
+
+ /**
+ * Gets the current Wifi display status.
+ * Watch for changes in the status by registering a broadcast receiver for
+ * {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED}.
+ *
+ * @return The current Wifi display status.
+ * @hide
+ */
+ public WifiDisplayStatus getWifiDisplayStatus() {
+ return mGlobal.getWifiDisplayStatus();
+ }
+
+ /**
+ * Listens for changes in available display devices.
+ */
+ public interface DisplayListener {
+ /**
+ * Called whenever a logical display has been added to the system.
+ * Use {@link DisplayManager#getDisplay} to get more information about
+ * the display.
+ *
+ * @param displayId The id of the logical display that was added.
+ */
+ void onDisplayAdded(int displayId);
+
+ /**
+ * Called whenever a logical display has been removed from the system.
+ *
+ * @param displayId The id of the logical display that was removed.
+ */
+ void onDisplayRemoved(int displayId);
+
+ /**
+ * Called whenever the properties of a logical display have changed.
+ *
+ * @param displayId The id of the logical display that changed.
+ */
+ void onDisplayChanged(int displayId);
+ }
+}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
new file mode 100644
index 0000000..a858681
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -0,0 +1,360 @@
+/*
+ * 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.content.Context;
+import android.hardware.display.DisplayManager.DisplayListener;
+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.util.Log;
+import android.util.SparseArray;
+import android.view.CompatibilityInfoHolder;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import java.util.ArrayList;
+
+/**
+ * Manager communication with the display manager service on behalf of
+ * an application process. You're probably looking for {@link DisplayManager}.
+ *
+ * @hide
+ */
+public final class DisplayManagerGlobal {
+ private static final String TAG = "DisplayManager";
+ private static final boolean DEBUG = false;
+
+ // True if display info and display ids should be cached.
+ //
+ // FIXME: The cache is currently disabled because it's unclear whether we have the
+ // necessary guarantees that the caches will always be flushed before clients
+ // attempt to observe their new state. For example, depending on the order
+ // in which the binder transactions take place, we might have a problem where
+ // an application could start processing a configuration change due to a display
+ // orientation change before the display info cache has actually been invalidated.
+ private static final boolean USE_CACHE = false;
+
+ public static final int EVENT_DISPLAY_ADDED = 1;
+ public static final int EVENT_DISPLAY_CHANGED = 2;
+ public static final int EVENT_DISPLAY_REMOVED = 3;
+
+ private static DisplayManagerGlobal sInstance;
+
+ private final Object mLock = new Object();
+
+ private final IDisplayManager mDm;
+
+ private DisplayManagerCallback mCallback;
+ private final ArrayList<DisplayListenerDelegate> mDisplayListeners =
+ new ArrayList<DisplayListenerDelegate>();
+
+ private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>();
+ private int[] mDisplayIdCache;
+
+ private DisplayManagerGlobal(IDisplayManager dm) {
+ mDm = dm;
+ }
+
+ /**
+ * Gets an instance of the display manager global singleton.
+ *
+ * @return The display manager instance, may be null early in system startup
+ * before the display manager has been fully initialized.
+ */
+ public static DisplayManagerGlobal getInstance() {
+ synchronized (DisplayManagerGlobal.class) {
+ if (sInstance == null) {
+ IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
+ if (b != null) {
+ sInstance = new DisplayManagerGlobal(IDisplayManager.Stub.asInterface(b));
+ }
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Get information about a particular logical display.
+ *
+ * @param displayId The logical display id.
+ * @return Information about the specified display, or null if it does not exist.
+ * This object belongs to an internal cache and should be treated as if it were immutable.
+ */
+ public DisplayInfo getDisplayInfo(int displayId) {
+ try {
+ synchronized (mLock) {
+ DisplayInfo info;
+ if (USE_CACHE) {
+ info = mDisplayInfoCache.get(displayId);
+ if (info != null) {
+ return info;
+ }
+ }
+
+ info = mDm.getDisplayInfo(displayId);
+ if (info == null) {
+ return null;
+ }
+
+ if (USE_CACHE) {
+ mDisplayInfoCache.put(displayId, info);
+ }
+ registerCallbackIfNeededLocked();
+
+ if (DEBUG) {
+ Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
+ }
+ return info;
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Could not get display information from display manager.", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Gets all currently valid logical display ids.
+ *
+ * @return An array containing all display ids.
+ */
+ public int[] getDisplayIds() {
+ try {
+ synchronized (mLock) {
+ if (USE_CACHE) {
+ if (mDisplayIdCache != null) {
+ return mDisplayIdCache;
+ }
+ }
+
+ int[] displayIds = mDm.getDisplayIds();
+ if (USE_CACHE) {
+ mDisplayIdCache = displayIds;
+ }
+ registerCallbackIfNeededLocked();
+ return displayIds;
+ }
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Could not get display ids from display manager.", ex);
+ return new int[] { Display.DEFAULT_DISPLAY };
+ }
+ }
+
+ /**
+ * Gets information about a logical display.
+ *
+ * The display metrics may be adjusted to provide compatibility
+ * for legacy applications.
+ *
+ * @param displayId The logical display id.
+ * @param cih The compatibility info, or null if none is required.
+ * @return The display object, or null if there is no display with the given id.
+ */
+ public Display getCompatibleDisplay(int displayId, CompatibilityInfoHolder cih) {
+ DisplayInfo displayInfo = getDisplayInfo(displayId);
+ if (displayInfo == null) {
+ return null;
+ }
+ return new Display(this, displayId, displayInfo, cih);
+ }
+
+ /**
+ * Gets information about a logical display without applying any compatibility metrics.
+ *
+ * @param displayId The logical display id.
+ * @return The display object, or null if there is no display with the given id.
+ */
+ public Display getRealDisplay(int displayId) {
+ return getCompatibleDisplay(displayId, null);
+ }
+
+ public void registerDisplayListener(DisplayListener listener, Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mLock) {
+ int index = findDisplayListenerLocked(listener);
+ if (index < 0) {
+ mDisplayListeners.add(new DisplayListenerDelegate(listener, handler));
+ registerCallbackIfNeededLocked();
+ }
+ }
+ }
+
+ public void unregisterDisplayListener(DisplayListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mLock) {
+ int index = findDisplayListenerLocked(listener);
+ if (index >= 0) {
+ DisplayListenerDelegate d = mDisplayListeners.get(index);
+ d.clearEvents();
+ mDisplayListeners.remove(index);
+ }
+ }
+ }
+
+ private int findDisplayListenerLocked(DisplayListener listener) {
+ final int numListeners = mDisplayListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (mDisplayListeners.get(i).mListener == listener) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private void registerCallbackIfNeededLocked() {
+ if (mCallback == null) {
+ mCallback = new DisplayManagerCallback();
+ try {
+ mDm.registerCallback(mCallback);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to register callback with display manager service.", ex);
+ mCallback = null;
+ }
+ }
+ }
+
+ private void handleDisplayEvent(int displayId, int event) {
+ synchronized (mLock) {
+ if (USE_CACHE) {
+ mDisplayInfoCache.remove(displayId);
+
+ if (event == EVENT_DISPLAY_ADDED || event == EVENT_DISPLAY_REMOVED) {
+ mDisplayIdCache = null;
+ }
+ }
+
+ final int numListeners = mDisplayListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ mDisplayListeners.get(i).sendDisplayEvent(displayId, event);
+ }
+ }
+ }
+
+ public void scanWifiDisplays() {
+ try {
+ mDm.scanWifiDisplays();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to scan for Wifi displays.", ex);
+ }
+ }
+
+ public void connectWifiDisplay(String deviceAddress) {
+ if (deviceAddress == null) {
+ throw new IllegalArgumentException("deviceAddress must not be null");
+ }
+
+ try {
+ mDm.connectWifiDisplay(deviceAddress);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to connect to Wifi display " + deviceAddress + ".", ex);
+ }
+ }
+
+ public void disconnectWifiDisplay() {
+ try {
+ mDm.disconnectWifiDisplay();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to disconnect from Wifi display.", ex);
+ }
+ }
+
+ public void renameWifiDisplay(String deviceAddress, String alias) {
+ if (deviceAddress == null) {
+ throw new IllegalArgumentException("deviceAddress must not be null");
+ }
+
+ try {
+ mDm.renameWifiDisplay(deviceAddress, alias);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to rename Wifi display " + deviceAddress
+ + " with alias " + alias + ".", ex);
+ }
+ }
+
+ public void forgetWifiDisplay(String deviceAddress) {
+ if (deviceAddress == null) {
+ throw new IllegalArgumentException("deviceAddress must not be null");
+ }
+
+ try {
+ mDm.forgetWifiDisplay(deviceAddress);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to forget Wifi display.", ex);
+ }
+ }
+
+ public WifiDisplayStatus getWifiDisplayStatus() {
+ try {
+ return mDm.getWifiDisplayStatus();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to get Wifi display status.", ex);
+ return new WifiDisplayStatus();
+ }
+ }
+
+ private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
+ @Override
+ public void onDisplayEvent(int displayId, int event) {
+ if (DEBUG) {
+ Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + event);
+ }
+ handleDisplayEvent(displayId, event);
+ }
+ }
+
+ private static final class DisplayListenerDelegate extends Handler {
+ public final DisplayListener mListener;
+
+ public DisplayListenerDelegate(DisplayListener listener, Handler handler) {
+ super(handler != null ? handler.getLooper() : Looper.myLooper(), null, true /*async*/);
+ mListener = listener;
+ }
+
+ public void sendDisplayEvent(int displayId, int event) {
+ Message msg = obtainMessage(event, displayId, 0);
+ sendMessage(msg);
+ }
+
+ public void clearEvents() {
+ removeCallbacksAndMessages(null);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_DISPLAY_ADDED:
+ mListener.onDisplayAdded(msg.arg1);
+ break;
+ case EVENT_DISPLAY_CHANGED:
+ mListener.onDisplayChanged(msg.arg1);
+ break;
+ case EVENT_DISPLAY_REMOVED:
+ mListener.onDisplayRemoved(msg.arg1);
+ break;
+ }
+ }
+ }
+}
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
new file mode 100644
index 0000000..79aad78
--- /dev/null
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -0,0 +1,49 @@
+/*
+ * 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.hardware.display.IDisplayManagerCallback;
+import android.hardware.display.WifiDisplay;
+import android.hardware.display.WifiDisplayStatus;
+import android.view.DisplayInfo;
+
+/** @hide */
+interface IDisplayManager {
+ DisplayInfo getDisplayInfo(int displayId);
+ int[] getDisplayIds();
+
+ void registerCallback(in IDisplayManagerCallback callback);
+
+ // No permissions required.
+ void scanWifiDisplays();
+
+ // Requires CONFIGURE_WIFI_DISPLAY permission to connect to an unknown device.
+ // No permissions required to connect to a known device.
+ void connectWifiDisplay(String address);
+
+ // No permissions required.
+ void disconnectWifiDisplay();
+
+ // Requires CONFIGURE_WIFI_DISPLAY permission.
+ void renameWifiDisplay(String address, String alias);
+
+ // Requires CONFIGURE_WIFI_DISPLAY permission.
+ void forgetWifiDisplay(String address);
+
+ // No permissions required.
+ WifiDisplayStatus getWifiDisplayStatus();
+}
diff --git a/core/java/android/hardware/display/IDisplayManagerCallback.aidl b/core/java/android/hardware/display/IDisplayManagerCallback.aidl
new file mode 100644
index 0000000..c50e3fb
--- /dev/null
+++ b/core/java/android/hardware/display/IDisplayManagerCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+/** @hide */
+interface IDisplayManagerCallback {
+ oneway void onDisplayEvent(int displayId, int event);
+}
diff --git a/core/java/android/hardware/display/WifiDisplay.aidl b/core/java/android/hardware/display/WifiDisplay.aidl
new file mode 100644
index 0000000..7733075
--- /dev/null
+++ b/core/java/android/hardware/display/WifiDisplay.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable WifiDisplay;
diff --git a/core/java/android/hardware/display/WifiDisplay.java b/core/java/android/hardware/display/WifiDisplay.java
new file mode 100644
index 0000000..0138b1c
--- /dev/null
+++ b/core/java/android/hardware/display/WifiDisplay.java
@@ -0,0 +1,137 @@
+/*
+ * 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;
+
+import libcore.util.Objects;
+
+/**
+ * Describes the properties of a Wifi display.
+ * <p>
+ * This object is immutable.
+ * </p>
+ *
+ * @hide
+ */
+public final class WifiDisplay implements Parcelable {
+ private final String mDeviceAddress;
+ private final String mDeviceName;
+ private final String mDeviceAlias;
+
+ public static final WifiDisplay[] EMPTY_ARRAY = new WifiDisplay[0];
+
+ public static final Creator<WifiDisplay> CREATOR = new Creator<WifiDisplay>() {
+ public WifiDisplay createFromParcel(Parcel in) {
+ String deviceAddress = in.readString();
+ String deviceName = in.readString();
+ String deviceAlias = in.readString();
+ return new WifiDisplay(deviceAddress, deviceName, deviceAlias);
+ }
+
+ public WifiDisplay[] newArray(int size) {
+ return size == 0 ? EMPTY_ARRAY : new WifiDisplay[size];
+ }
+ };
+
+ public WifiDisplay(String deviceAddress, String deviceName, String deviceAlias) {
+ if (deviceAddress == null) {
+ throw new IllegalArgumentException("deviceAddress must not be null");
+ }
+ if (deviceName == null) {
+ throw new IllegalArgumentException("deviceName must not be null");
+ }
+
+ mDeviceAddress = deviceAddress;
+ mDeviceName = deviceName;
+ mDeviceAlias = deviceAlias;
+ }
+
+ /**
+ * Gets the MAC address of the Wifi display device.
+ */
+ public String getDeviceAddress() {
+ return mDeviceAddress;
+ }
+
+ /**
+ * Gets the name of the Wifi display device.
+ */
+ public String getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * Gets the user-specified alias of the Wifi display device, or null if none.
+ * <p>
+ * The alias should be used in the UI whenever available. It is the value
+ * provided by the user when renaming the device.
+ * </p>
+ */
+ public String getDeviceAlias() {
+ return mDeviceAlias;
+ }
+
+ /**
+ * Gets the name to show in the UI.
+ * Uses the device alias if available, otherwise uses the device name.
+ */
+ public String getFriendlyDisplayName() {
+ return mDeviceAlias != null ? mDeviceAlias : mDeviceName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof WifiDisplay && equals((WifiDisplay)o);
+ }
+
+ public boolean equals(WifiDisplay other) {
+ return other != null
+ && mDeviceAddress.equals(other.mDeviceAddress)
+ && mDeviceName.equals(other.mDeviceName)
+ && Objects.equal(mDeviceAlias, other.mDeviceAlias);
+ }
+
+ @Override
+ public int hashCode() {
+ // The address on its own should be sufficiently unique for hashing purposes.
+ return mDeviceAddress.hashCode();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mDeviceAddress);
+ dest.writeString(mDeviceName);
+ dest.writeString(mDeviceAlias);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ // For debugging purposes only.
+ @Override
+ public String toString() {
+ String result = mDeviceName + " (" + mDeviceAddress + ")";
+ if (mDeviceAlias != null) {
+ result += ", alias " + mDeviceAlias;
+ }
+ return result;
+ }
+}
diff --git a/core/java/android/hardware/display/WifiDisplayStatus.aidl b/core/java/android/hardware/display/WifiDisplayStatus.aidl
new file mode 100644
index 0000000..35c633e
--- /dev/null
+++ b/core/java/android/hardware/display/WifiDisplayStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable WifiDisplayStatus;
diff --git a/core/java/android/hardware/display/WifiDisplayStatus.java b/core/java/android/hardware/display/WifiDisplayStatus.java
new file mode 100644
index 0000000..f7e72c4
--- /dev/null
+++ b/core/java/android/hardware/display/WifiDisplayStatus.java
@@ -0,0 +1,215 @@
+/*
+ * 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;
+
+import java.util.Arrays;
+
+/**
+ * Describes the current global state of Wifi display connectivity, including the
+ * currently connected display and all available or remembered displays.
+ * <p>
+ * This object is immutable.
+ * </p>
+ *
+ * @hide
+ */
+public final class WifiDisplayStatus implements Parcelable {
+ private final int mFeatureState;
+ private final int mScanState;
+ private final int mActiveDisplayState;
+ private final WifiDisplay mActiveDisplay;
+ private final WifiDisplay[] mAvailableDisplays;
+ private final WifiDisplay[] mRememberedDisplays;
+
+ /** Feature state: Wifi display is not available on this device. */
+ public static final int FEATURE_STATE_UNAVAILABLE = 0;
+ /** Feature state: Wifi display is disabled, probably because Wifi is disabled. */
+ public static final int FEATURE_STATE_DISABLED = 1;
+ /** Feature state: Wifi display is turned off in settings. */
+ public static final int FEATURE_STATE_OFF = 2;
+ /** Feature state: Wifi display is turned on in settings. */
+ public static final int FEATURE_STATE_ON = 3;
+
+ /** Scan state: Not currently scanning. */
+ public static final int SCAN_STATE_NOT_SCANNING = 0;
+ /** Scan state: Currently scanning. */
+ public static final int SCAN_STATE_SCANNING = 1;
+
+ /** Display state: Not connected. */
+ public static final int DISPLAY_STATE_NOT_CONNECTED = 0;
+ /** Display state: Connecting to active display. */
+ public static final int DISPLAY_STATE_CONNECTING = 1;
+ /** Display state: Connected to active display. */
+ public static final int DISPLAY_STATE_CONNECTED = 2;
+
+ public static final Creator<WifiDisplayStatus> CREATOR = new Creator<WifiDisplayStatus>() {
+ public WifiDisplayStatus createFromParcel(Parcel in) {
+ int featureState = in.readInt();
+ int scanState = in.readInt();
+ int activeDisplayState= in.readInt();
+
+ WifiDisplay activeDisplay = null;
+ if (in.readInt() != 0) {
+ 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[] rememberedDisplays = WifiDisplay.CREATOR.newArray(in.readInt());
+ for (int i = 0; i < rememberedDisplays.length; i++) {
+ rememberedDisplays[i] = WifiDisplay.CREATOR.createFromParcel(in);
+ }
+
+ return new WifiDisplayStatus(featureState, scanState, activeDisplayState,
+ activeDisplay, availableDisplays, rememberedDisplays);
+ }
+
+ public WifiDisplayStatus[] newArray(int size) {
+ return new WifiDisplayStatus[size];
+ }
+ };
+
+ public WifiDisplayStatus() {
+ this(FEATURE_STATE_UNAVAILABLE, SCAN_STATE_NOT_SCANNING, DISPLAY_STATE_NOT_CONNECTED,
+ null, WifiDisplay.EMPTY_ARRAY, WifiDisplay.EMPTY_ARRAY);
+ }
+
+ 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");
+ }
+
+ mFeatureState = featureState;
+ mScanState = scanState;
+ mActiveDisplayState = activeDisplayState;
+ mActiveDisplay = activeDisplay;
+ mAvailableDisplays = availableDisplays;
+ mRememberedDisplays = rememberedDisplays;
+ }
+
+ /**
+ * Returns the state of the Wifi display feature on this device.
+ * <p>
+ * The value of this property reflects whether the device supports the Wifi display,
+ * whether it has been enabled by the user and whether the prerequisites for
+ * connecting to displays have been met.
+ * </p>
+ */
+ public int getFeatureState() {
+ return mFeatureState;
+ }
+
+ /**
+ * Returns the current state of the Wifi display scan.
+ *
+ * @return One of: {@link #SCAN_STATE_NOT_SCANNING} or {@link #SCAN_STATE_SCANNING}.
+ */
+ public int getScanState() {
+ return mScanState;
+ }
+
+ /**
+ * Get the state of the currently active display.
+ *
+ * @return One of: {@link #DISPLAY_STATE_NOT_CONNECTED}, {@link #DISPLAY_STATE_CONNECTING},
+ * or {@link #DISPLAY_STATE_CONNECTED}.
+ */
+ public int getActiveDisplayState() {
+ return mActiveDisplayState;
+ }
+
+ /**
+ * Gets the Wifi display that is currently active. It may be connecting or
+ * connected.
+ */
+ public WifiDisplay getActiveDisplay() {
+ return mActiveDisplay;
+ }
+
+ /**
+ * Gets the list of all available Wifi displays as reported by the most recent
+ * scan, never null.
+ * <p>
+ * Some of these displays may already be remembered, others may be unknown.
+ * </p>
+ */
+ public WifiDisplay[] getAvailableDisplays() {
+ return mAvailableDisplays;
+ }
+
+ /**
+ * Gets the list of all remembered Wifi displays, never null.
+ * <p>
+ * Not all remembered displays will necessarily be available.
+ * </p>
+ */
+ public WifiDisplay[] getRememberedDisplays() {
+ return mRememberedDisplays;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mFeatureState);
+ dest.writeInt(mScanState);
+ dest.writeInt(mActiveDisplayState);
+
+ if (mActiveDisplay != null) {
+ dest.writeInt(1);
+ mActiveDisplay.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+
+ dest.writeInt(mAvailableDisplays.length);
+ for (WifiDisplay display : mAvailableDisplays) {
+ display.writeToParcel(dest, flags);
+ }
+
+ dest.writeInt(mRememberedDisplays.length);
+ for (WifiDisplay display : mRememberedDisplays) {
+ display.writeToParcel(dest, flags);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ // For debugging purposes only.
+ @Override
+ public String toString() {
+ return "WifiDisplayStatus{featureState=" + mFeatureState
+ + ", scanState=" + mScanState
+ + ", activeDisplayState=" + mActiveDisplayState
+ + ", activeDisplay=" + mActiveDisplay
+ + ", availableDisplays=" + Arrays.toString(mAvailableDisplays)
+ + ", rememberedDisplays=" + Arrays.toString(mRememberedDisplays)
+ + "}";
+ }
+}
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 9bab797..98bd4f5 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -87,4 +87,12 @@ interface IUsbManager
/* Sets the file path for USB mass storage backing file. */
void setMassStorageBackingFile(String path);
+
+ /* Allow USB debugging from the attached host. If alwaysAllow is true, add the
+ * the public key to list of host keys that the user has approved.
+ */
+ void allowUsbDebugging(boolean alwaysAllow, String publicKey);
+
+ /* Deny USB debugging from the attached host */
+ void denyUsbDebugging();
}
diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java
index 2252248..3646715 100644
--- a/core/java/android/hardware/usb/UsbRequest.java
+++ b/core/java/android/hardware/usb/UsbRequest.java
@@ -152,10 +152,14 @@ public class UsbRequest {
/* package */ void dequeue() {
boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
+ int bytesRead;
if (mBuffer.isDirect()) {
- native_dequeue_direct();
+ bytesRead = native_dequeue_direct();
} else {
- native_dequeue_array(mBuffer.array(), mLength, out);
+ bytesRead = native_dequeue_array(mBuffer.array(), mLength, out);
+ }
+ if (bytesRead >= 0) {
+ mBuffer.position(Math.min(bytesRead, mLength));
}
mBuffer = null;
mLength = 0;
@@ -174,8 +178,8 @@ public class UsbRequest {
int ep_attributes, int ep_max_packet_size, int ep_interval);
private native void native_close();
private native boolean native_queue_array(byte[] buffer, int length, boolean out);
- private native void native_dequeue_array(byte[] buffer, int length, boolean out);
+ private native int native_dequeue_array(byte[] buffer, int length, boolean out);
private native boolean native_queue_direct(ByteBuffer buffer, int length, boolean out);
- private native void native_dequeue_direct();
+ private native int native_dequeue_direct();
private native boolean native_cancel();
}
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index 27af013..3c3182a 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -149,6 +149,17 @@ public abstract class AbstractInputMethodService extends Service
callback.finishedEvent(seq, handled);
}
}
+
+ /**
+ * Take care of dispatching incoming generic motion events to the appropriate
+ * callbacks on the service, and tell the client when this is done.
+ */
+ public void dispatchGenericMotionEvent(int seq, MotionEvent event, EventCallback callback) {
+ boolean handled = onGenericMotionEvent(event);
+ if (callback != null) {
+ callback.finishedEvent(seq, handled);
+ }
+ }
}
/**
@@ -189,7 +200,25 @@ public abstract class AbstractInputMethodService extends Service
return new IInputMethodWrapper(this, mInputMethod);
}
+ /**
+ * Implement this to handle trackball events on your input method.
+ *
+ * @param event The motion event being received.
+ * @return True if the event was handled in this function, false otherwise.
+ * @see View#onTrackballEvent
+ */
public boolean onTrackballEvent(MotionEvent event) {
return false;
}
+
+ /**
+ * Implement this to handle generic motion events on your input method.
+ *
+ * @param event The motion event being received.
+ * @return True if the event was handled in this function, false otherwise.
+ * @see View#onGenericMotionEvent
+ */
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ return false;
+ }
}
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index e10f218..5324f81 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -17,6 +17,7 @@
package android.inputmethodservice;
import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInputMethodCallback;
import com.android.internal.view.IInputMethodSession;
@@ -42,6 +43,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
private static final int DO_UPDATE_EXTRACTED_TEXT = 67;
private static final int DO_DISPATCH_KEY_EVENT = 70;
private static final int DO_DISPATCH_TRACKBALL_EVENT = 80;
+ private static final int DO_DISPATCH_GENERIC_MOTION_EVENT = 85;
private static final int DO_UPDATE_SELECTION = 90;
private static final int DO_UPDATE_CURSOR = 95;
private static final int DO_APP_PRIVATE_COMMAND = 100;
@@ -91,28 +93,37 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
(ExtractedText)msg.obj);
return;
case DO_DISPATCH_KEY_EVENT: {
- HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ SomeArgs args = (SomeArgs)msg.obj;
mInputMethodSession.dispatchKeyEvent(msg.arg1,
(KeyEvent)args.arg1,
new InputMethodEventCallbackWrapper(
(IInputMethodCallback)args.arg2));
- mCaller.recycleArgs(args);
+ args.recycle();
return;
}
case DO_DISPATCH_TRACKBALL_EVENT: {
- HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ SomeArgs args = (SomeArgs)msg.obj;
mInputMethodSession.dispatchTrackballEvent(msg.arg1,
(MotionEvent)args.arg1,
new InputMethodEventCallbackWrapper(
(IInputMethodCallback)args.arg2));
- mCaller.recycleArgs(args);
+ args.recycle();
+ return;
+ }
+ case DO_DISPATCH_GENERIC_MOTION_EVENT: {
+ SomeArgs args = (SomeArgs)msg.obj;
+ mInputMethodSession.dispatchGenericMotionEvent(msg.arg1,
+ (MotionEvent)args.arg1,
+ new InputMethodEventCallbackWrapper(
+ (IInputMethodCallback)args.arg2));
+ args.recycle();
return;
}
case DO_UPDATE_SELECTION: {
- HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ SomeArgs args = (SomeArgs)msg.obj;
mInputMethodSession.updateSelection(args.argi1, args.argi2,
args.argi3, args.argi4, args.argi5, args.argi6);
- mCaller.recycleArgs(args);
+ args.recycle();
return;
}
case DO_UPDATE_CURSOR: {
@@ -120,10 +131,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
return;
}
case DO_APP_PRIVATE_COMMAND: {
- HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ SomeArgs args = (SomeArgs)msg.obj;
mInputMethodSession.appPrivateCommand((String)args.arg1,
(Bundle)args.arg2);
- mCaller.recycleArgs(args);
+ args.recycle();
return;
}
case DO_TOGGLE_SOFT_INPUT: {
@@ -166,6 +177,12 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
event, callback));
}
+ public void dispatchGenericMotionEvent(int seq, MotionEvent event,
+ IInputMethodCallback callback) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_DISPATCH_GENERIC_MOTION_EVENT, seq,
+ event, callback));
+ }
+
public void updateSelection(int oldSelStart, int oldSelEnd,
int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIIIIII(DO_UPDATE_SELECTION,
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 17c9ee7..5275314 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -17,6 +17,7 @@
package android.inputmethodservice;
import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodCallback;
@@ -124,7 +125,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
if (target == null) {
return;
}
- HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ SomeArgs args = (SomeArgs)msg.obj;
try {
target.dump((FileDescriptor)args.arg1,
(PrintWriter)args.arg2, (String[])args.arg3);
@@ -134,6 +135,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
synchronized (args.arg4) {
((CountDownLatch)args.arg4).countDown();
}
+ args.recycle();
return;
}
@@ -149,23 +151,25 @@ class IInputMethodWrapper extends IInputMethod.Stub
inputMethod.unbindInput();
return;
case DO_START_INPUT: {
- HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ SomeArgs args = (SomeArgs)msg.obj;
IInputContext inputContext = (IInputContext)args.arg1;
InputConnection ic = inputContext != null
? new InputConnectionWrapper(inputContext) : null;
EditorInfo info = (EditorInfo)args.arg2;
info.makeCompatible(mTargetSdkVersion);
inputMethod.startInput(ic, info);
+ args.recycle();
return;
}
case DO_RESTART_INPUT: {
- HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ SomeArgs args = (SomeArgs)msg.obj;
IInputContext inputContext = (IInputContext)args.arg1;
InputConnection ic = inputContext != null
? new InputConnectionWrapper(inputContext) : null;
EditorInfo info = (EditorInfo)args.arg2;
info.makeCompatible(mTargetSdkVersion);
inputMethod.restartInput(ic, info);
+ args.recycle();
return;
}
case DO_CREATE_SESSION: {
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 46153e7..f07002e 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -19,6 +19,7 @@ package android.inputmethodservice;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import android.app.ActivityManager;
import android.app.Dialog;
import android.content.Context;
import android.content.res.Configuration;
@@ -250,6 +251,7 @@ public class InputMethodService extends AbstractInputMethodService {
InputMethodManager mImm;
int mTheme = 0;
+ boolean mHardwareAccelerated = false;
LayoutInflater mInflater;
TypedArray mThemeAttrs;
@@ -614,6 +616,26 @@ public class InputMethodService extends AbstractInputMethodService {
mTheme = theme;
}
+ /**
+ * You can call this to try to enable hardware accelerated drawing for
+ * your IME. This must be set before {@link #onCreate}, so you
+ * will typically call it in your constructor. It is not always possible
+ * to use hardware acclerated drawing in an IME (for example on low-end
+ * devices that do not have the resources to support this), so the call
+ * returns true if it succeeds otherwise false if you will need to draw
+ * in software. You must be able to handle either case.
+ */
+ public boolean enableHardwareAcceleration() {
+ if (mWindow != null) {
+ throw new IllegalStateException("Must be called before onCreate()");
+ }
+ if (ActivityManager.isHighEndGfx()) {
+ mHardwareAccelerated = true;
+ return true;
+ }
+ return false;
+ }
+
@Override public void onCreate() {
mTheme = Resources.selectSystemTheme(mTheme,
getApplicationInfo().targetSdkVersion,
@@ -626,6 +648,9 @@ public class InputMethodService extends AbstractInputMethodService {
mInflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
+ if (mHardwareAccelerated) {
+ mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ }
initViews();
mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
}
@@ -658,8 +683,8 @@ public class InputMethodService extends AbstractInputMethodService {
com.android.internal.R.layout.input_method, null);
mWindow.setContentView(mRootView);
mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
- if (Settings.System.getInt(getContentResolver(),
- Settings.System.FANCY_IME_ANIMATIONS, 0) != 0) {
+ if (Settings.Global.getInt(getContentResolver(),
+ Settings.Global.FANCY_IME_ANIMATIONS, 0) != 0) {
mWindow.getWindow().setWindowAnimations(
com.android.internal.R.style.Animation_InputMethodFancy);
}
@@ -1674,9 +1699,6 @@ public class InputMethodService extends AbstractInputMethodService {
/**
* Show the input method. This is a call back to the
* IMF to handle showing the input method.
- * Close this input method's soft input area, removing it from the display.
- * The input method will continue running, but the user can no longer use
- * it to generate input by touching the screen.
* @param flags Provides additional operating flags. Currently may be
* 0 or have the {@link InputMethodManager#SHOW_FORCED
* InputMethodManager.} bit set.
@@ -1751,7 +1773,7 @@ public class InputMethodService extends AbstractInputMethodService {
* Override this to intercept special key multiple events before they are
* processed by the
* application. If you return true, the application will not itself
- * process the event. If you return true, the normal application processing
+ * process the event. If you return false, the normal application processing
* will occur as if the IME had not seen the event at all.
*
* <p>The default implementation always returns false, except when
@@ -1766,7 +1788,7 @@ public class InputMethodService extends AbstractInputMethodService {
/**
* Override this to intercept key up events before they are processed by the
* application. If you return true, the application will not itself
- * process the event. If you return true, the normal application processing
+ * process the event. If you return false, the normal application processing
* will occur as if the IME had not seen the event at all.
*
* <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK
@@ -1784,8 +1806,29 @@ public class InputMethodService extends AbstractInputMethodService {
return doMovementKey(keyCode, event, MOVEMENT_UP);
}
+ /**
+ * Override this to intercept trackball motion events before they are
+ * processed by the application.
+ * If you return true, the application will not itself process the event.
+ * If you return false, the normal application processing will occur as if
+ * the IME had not seen the event at all.
+ */
@Override
public boolean onTrackballEvent(MotionEvent event) {
+ if (DEBUG) Log.v(TAG, "onTrackballEvent: " + event);
+ return false;
+ }
+
+ /**
+ * Override this to intercept generic motion events before they are
+ * processed by the application.
+ * If you return true, the application will not itself process the event.
+ * If you return false, the normal application processing will occur as if
+ * the IME had not seen the event at all.
+ */
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ if (DEBUG) Log.v(TAG, "onGenericMotionEvent(): event " + event);
return false;
}
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
new file mode 100644
index 0000000..4b60f07
--- /dev/null
+++ b/core/java/android/net/BaseNetworkStateTracker.java
@@ -0,0 +1,158 @@
+/*
+ * 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.net;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Interface to control and observe state of a specific network, hiding
+ * network-specific details from {@link ConnectivityManager}. Surfaces events
+ * through the registered {@link Handler} to enable {@link ConnectivityManager}
+ * to respond to state changes over time.
+ *
+ * @hide
+ */
+public abstract class BaseNetworkStateTracker implements NetworkStateTracker {
+ // TODO: better document threading expectations
+ // TODO: migrate to make NetworkStateTracker abstract class
+
+ public static final String PROP_TCP_BUFFER_UNKNOWN = "net.tcp.buffersize.unknown";
+ public static final String PROP_TCP_BUFFER_WIFI = "net.tcp.buffersize.wifi";
+
+ protected Context mContext;
+ private Handler mTarget;
+
+ protected NetworkInfo mNetworkInfo;
+ protected LinkProperties mLinkProperties;
+ protected LinkCapabilities mLinkCapabilities;
+
+ private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
+ private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
+ private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
+
+ public BaseNetworkStateTracker(int networkType) {
+ mNetworkInfo = new NetworkInfo(
+ networkType, -1, ConnectivityManager.getNetworkTypeName(networkType), null);
+ mLinkProperties = new LinkProperties();
+ mLinkCapabilities = new LinkCapabilities();
+ }
+
+ @Deprecated
+ protected Handler getTargetHandler() {
+ return mTarget;
+ }
+
+ protected final void dispatchStateChanged() {
+ // TODO: include snapshot of other fields when sending
+ mTarget.obtainMessage(EVENT_STATE_CHANGED, getNetworkInfo()).sendToTarget();
+ }
+
+ protected final void dispatchConfigurationChanged() {
+ // TODO: include snapshot of other fields when sending
+ mTarget.obtainMessage(EVENT_CONFIGURATION_CHANGED, getNetworkInfo()).sendToTarget();
+ }
+
+ @Override
+ public final void startMonitoring(Context context, Handler target) {
+ mContext = Preconditions.checkNotNull(context);
+ mTarget = Preconditions.checkNotNull(target);
+ startMonitoringInternal();
+ }
+
+ protected abstract void startMonitoringInternal();
+
+ @Override
+ public final NetworkInfo getNetworkInfo() {
+ return new NetworkInfo(mNetworkInfo);
+ }
+
+ @Override
+ public final LinkProperties getLinkProperties() {
+ return new LinkProperties(mLinkProperties);
+ }
+
+ @Override
+ public final LinkCapabilities getLinkCapabilities() {
+ return new LinkCapabilities(mLinkCapabilities);
+ }
+
+ @Override
+ public void captivePortalCheckComplete() {
+ // not implemented
+ }
+
+ @Override
+ public boolean setRadio(boolean turnOn) {
+ // Base tracker doesn't handle radios
+ return true;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mNetworkInfo.isAvailable();
+ }
+
+ @Override
+ public void setUserDataEnable(boolean enabled) {
+ // Base tracker doesn't handle enabled flags
+ }
+
+ @Override
+ public void setPolicyDataEnable(boolean enabled) {
+ // Base tracker doesn't handle enabled flags
+ }
+
+ @Override
+ public boolean isPrivateDnsRouteSet() {
+ return mPrivateDnsRouteSet.get();
+ }
+
+ @Override
+ public void privateDnsRouteSet(boolean enabled) {
+ mPrivateDnsRouteSet.set(enabled);
+ }
+
+ @Override
+ public boolean isDefaultRouteSet() {
+ return mDefaultRouteSet.get();
+ }
+
+ @Override
+ public void defaultRouteSet(boolean enabled) {
+ mDefaultRouteSet.set(enabled);
+ }
+
+ @Override
+ public boolean isTeardownRequested() {
+ return mTeardownRequested.get();
+ }
+
+ @Override
+ public void setTeardownRequested(boolean isRequested) {
+ mTeardownRequested.set(isRequested);
+ }
+
+ @Override
+ public void setDependencyMet(boolean met) {
+ // Base tracker doesn't handle dependencies
+ }
+}
diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java
new file mode 100644
index 0000000..19ed658
--- /dev/null
+++ b/core/java/android/net/CaptivePortalTracker.java
@@ -0,0 +1,378 @@
+/*
+ * 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.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.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.os.UserHandle;
+import android.os.Message;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.URL;
+import java.net.UnknownHostException;
+
+import com.android.internal.R;
+
+/**
+ * This class allows captive portal detection on a network.
+ * @hide
+ */
+public class CaptivePortalTracker extends StateMachine {
+ private static final boolean DBG = false;
+ 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;
+
+ private String mServer;
+ private String mUrl;
+ private boolean mNotificationShown = false;
+ private boolean mIsCaptivePortalCheckEnabled = false;
+ private IConnectivityManager mConnService;
+ private TelephonyManager mTelephonyManager;
+ private Context mContext;
+ private NetworkInfo mNetworkInfo;
+
+ private static final int CMD_DETECT_PORTAL = 0;
+ private static final int CMD_CONNECTIVITY_CHANGE = 1;
+ private static final int CMD_DELAYED_CAPTIVE_CHECK = 2;
+
+ /* This delay happens every time before we do a captive check on a network */
+ private static final int DELAYED_CHECK_INTERVAL_MS = 10000;
+ private int mDelayedCheckToken = 0;
+
+ private State mDefaultState = new DefaultState();
+ private State mNoActiveNetworkState = new NoActiveNetworkState();
+ private State mActiveNetworkState = new ActiveNetworkState();
+ private State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState();
+
+ private CaptivePortalTracker(Context context, IConnectivityManager cs) {
+ super(TAG);
+
+ mContext = context;
+ mConnService = cs;
+ mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ mContext.registerReceiver(mReceiver, filter);
+
+ mServer = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.CAPTIVE_PORTAL_SERVER);
+ if (mServer == null) mServer = DEFAULT_SERVER;
+
+ mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
+
+ addState(mDefaultState);
+ addState(mNoActiveNetworkState, mDefaultState);
+ addState(mActiveNetworkState, mDefaultState);
+ addState(mDelayedCaptiveCheckState, mActiveNetworkState);
+ setInitialState(mNoActiveNetworkState);
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ NetworkInfo info = intent.getParcelableExtra(
+ ConnectivityManager.EXTRA_NETWORK_INFO);
+ sendMessage(obtainMessage(CMD_CONNECTIVITY_CHANGE, info));
+ }
+ }
+ };
+
+ public static CaptivePortalTracker makeCaptivePortalTracker(Context context,
+ IConnectivityManager cs) {
+ CaptivePortalTracker captivePortal = new CaptivePortalTracker(context, cs);
+ captivePortal.start();
+ return captivePortal;
+ }
+
+ public void detectCaptivePortal(NetworkInfo info) {
+ sendMessage(obtainMessage(CMD_DETECT_PORTAL, info));
+ }
+
+ 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");
+ switch (message.what) {
+ case CMD_DETECT_PORTAL:
+ NetworkInfo info = (NetworkInfo) message.obj;
+ // Checking on a secondary connection is not supported
+ // yet
+ notifyPortalCheckComplete(info);
+ break;
+ case CMD_CONNECTIVITY_CHANGE:
+ case CMD_DELAYED_CAPTIVE_CHECK:
+ break;
+ default:
+ loge("Ignoring " + message);
+ break;
+ }
+ return HANDLED;
+ }
+ }
+
+ private class NoActiveNetworkState extends State {
+ @Override
+ public void enter() {
+ if (DBG) log(getName() + "\n");
+ mNetworkInfo = null;
+ /* Clear any previous notification */
+ setNotificationVisible(false);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) log(getName() + message.toString() + "\n");
+ InetAddress server;
+ NetworkInfo info;
+ switch (message.what) {
+ case CMD_CONNECTIVITY_CHANGE:
+ info = (NetworkInfo) message.obj;
+ if (info.isConnected() && isActiveNetwork(info)) {
+ mNetworkInfo = info;
+ transitionTo(mDelayedCaptiveCheckState);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ private class ActiveNetworkState extends State {
+ @Override
+ public void enter() {
+ if (DBG) log(getName() + "\n");
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ NetworkInfo info;
+ switch (message.what) {
+ case CMD_CONNECTIVITY_CHANGE:
+ info = (NetworkInfo) message.obj;
+ if (!info.isConnected()
+ && info.getType() == mNetworkInfo.getType()) {
+ if (DBG) log("Disconnected from active network " + info);
+ transitionTo(mNoActiveNetworkState);
+ } else if (info.getType() != mNetworkInfo.getType() &&
+ info.isConnected() &&
+ isActiveNetwork(info)) {
+ if (DBG) log("Active network switched " + info);
+ deferMessage(message);
+ transitionTo(mNoActiveNetworkState);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+
+
+ private class DelayedCaptiveCheckState extends State {
+ @Override
+ public void enter() {
+ if (DBG) log(getName() + "\n");
+ sendMessageDelayed(obtainMessage(CMD_DELAYED_CAPTIVE_CHECK,
+ ++mDelayedCheckToken, 0), DELAYED_CHECK_INTERVAL_MS);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) log(getName() + message.toString() + "\n");
+ switch (message.what) {
+ case CMD_DELAYED_CAPTIVE_CHECK:
+ if (message.arg1 == mDelayedCheckToken) {
+ InetAddress server = lookupHost(mServer);
+ if (server != null) {
+ if (isCaptivePortal(server)) {
+ if (DBG) log("Captive network " + mNetworkInfo);
+ setNotificationVisible(true);
+ }
+ }
+ if (DBG) log("Not captive network " + mNetworkInfo);
+ transitionTo(mActiveNetworkState);
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ private void notifyPortalCheckComplete(NetworkInfo info) {
+ if (info == null) {
+ loge("notifyPortalCheckComplete on null");
+ return;
+ }
+ try {
+ mConnService.captivePortalCheckComplete(info);
+ } catch(RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private boolean isActiveNetwork(NetworkInfo info) {
+ try {
+ NetworkInfo active = mConnService.getActiveNetworkInfo();
+ if (active != null && active.getType() == info.getType()) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ /**
+ * Do a URL fetch on a known server to see if we get the data we expect
+ */
+ private boolean isCaptivePortal(InetAddress server) {
+ HttpURLConnection urlConnection = null;
+ if (!mIsCaptivePortalCheckEnabled) return false;
+
+ mUrl = "http://" + server.getHostAddress() + "/generate_204";
+ if (DBG) log("Checking " + mUrl);
+ try {
+ URL url = new URL(mUrl);
+ urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setInstanceFollowRedirects(false);
+ urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
+ urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
+ urlConnection.setUseCaches(false);
+ urlConnection.getInputStream();
+ // we got a valid response, but not from the real google
+ return urlConnection.getResponseCode() != 204;
+ } catch (IOException e) {
+ if (DBG) log("Probably not a portal: exception " + e);
+ return false;
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ }
+ }
+
+ private InetAddress lookupHost(String hostname) {
+ InetAddress inetAddress[];
+ try {
+ inetAddress = InetAddress.getAllByName(hostname);
+ } catch (UnknownHostException e) {
+ return null;
+ }
+
+ for (InetAddress a : inetAddress) {
+ if (a instanceof Inet4Address) return a;
+ }
+ return null;
+ }
+
+ private void setNotificationVisible(boolean visible) {
+ // if it should be hidden and it is already hidden, then noop
+ if (!visible && !mNotificationShown) {
+ return;
+ }
+
+ Resources r = Resources.getSystem();
+ NotificationManager notificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (visible) {
+ CharSequence title;
+ CharSequence details;
+ 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());
+ 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();
+ break;
+ default:
+ title = r.getString(R.string.network_available_sign_in, 0);
+ details = r.getString(R.string.network_available_sign_in_detailed,
+ mNetworkInfo.getExtraInfo());
+ break;
+ }
+
+ Notification notification = new Notification();
+ notification.when = 0;
+ notification.icon = com.android.internal.R.drawable.stat_notify_wifi_in_range;
+ 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);
+ }
+ mNotificationShown = visible;
+ }
+
+ private static void log(String s) {
+ Log.d(TAG, s);
+ }
+
+ private static void loge(String s) {
+ Log.e(TAG, s);
+ }
+
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 5f8793c..6ff1a33 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -89,11 +89,21 @@ public class ConnectivityManager {
* should always obtain network information through
* {@link #getActiveNetworkInfo()} or
* {@link #getAllNetworkInfo()}.
+ * @see #EXTRA_NETWORK_TYPE
*/
@Deprecated
public static final String EXTRA_NETWORK_INFO = "networkInfo";
/**
+ * Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast.
+ * Can be used with {@link #getNetworkInfo(int)} to get {@link NetworkInfo}
+ * state based on the calling application.
+ *
+ * @see android.content.Intent#getIntExtra(String, int)
+ */
+ public static final String EXTRA_NETWORK_TYPE = "networkType";
+
+ /**
* The lookup key for a boolean that indicates whether a connect event
* is for a network to which the connectivity manager was failing over
* following a disconnect on another network.
@@ -137,6 +147,28 @@ public class ConnectivityManager {
public static final String EXTRA_INET_CONDITION = "inetCondition";
/**
+ * Broadcast action to indicate the change of data activity status
+ * (idle or active) on a network in a recent period.
+ * The network becomes active when data transimission is started, or
+ * idle if there is no data transimition for a period of time.
+ * {@hide}
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DATA_ACTIVITY_CHANGE = "android.net.conn.DATA_ACTIVITY_CHANGE";
+ /**
+ * The lookup key for an enum that indicates the network device type on which this data activity
+ * change happens.
+ * {@hide}
+ */
+ public static final String EXTRA_DEVICE_TYPE = "deviceType";
+ /**
+ * The lookup key for a boolean that indicates the device is active or not. {@code true} means
+ * it is actively sending or receiving data and {@code false} means it is idle.
+ * {@hide}
+ */
+ public static final String EXTRA_IS_ACTIVE = "isActive";
+
+ /**
* Broadcast Action: The setting for background data usage has changed
* values. Use {@link #getBackgroundDataSetting()} to get the current value.
* <p>
@@ -298,6 +330,14 @@ public class ConnectivityManager {
public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
+ /**
+ * Default value for {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY} in
+ * milliseconds.
+ *
+ * @hide
+ */
+ public static final int CONNECTIVITY_CHANGE_DELAY_DEFAULT = 3000;
+
private final IConnectivityManager mService;
public static boolean isNetworkTypeValid(int networkType) {
@@ -880,4 +920,24 @@ public class ConnectivityManager {
return false;
}
}
+
+ /** {@hide} */
+ public boolean updateLockdownVpn() {
+ try {
+ return mService.updateLockdownVpn();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * {@hide}
+ */
+ public void captivePortalCheckComplete(NetworkInfo info) {
+ try {
+ mService.captivePortalCheckComplete(info);
+ } catch (RemoteException e) {
+ }
+ }
+
}
diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java
index cc3e34f..874e80a 100644
--- a/core/java/android/net/DhcpStateMachine.java
+++ b/core/java/android/net/DhcpStateMachine.java
@@ -92,10 +92,12 @@ public class DhcpStateMachine extends StateMachine {
/* Notification from DHCP state machine post DHCP discovery/renewal. Indicates
* success/failure */
public static final int CMD_POST_DHCP_ACTION = BASE + 5;
+ /* Notification from DHCP state machine before quitting */
+ public static final int CMD_ON_QUIT = BASE + 6;
/* Command from controller to indicate DHCP discovery/renewal can continue
* after pre DHCP action is complete */
- public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 6;
+ public static final int CMD_PRE_DHCP_ACTION_COMPLETE = BASE + 7;
/* Message.arg1 arguments to CMD_POST_DHCP notification */
public static final int DHCP_SUCCESS = 1;
@@ -172,6 +174,10 @@ public class DhcpStateMachine extends StateMachine {
quit();
}
+ protected void onQuitting() {
+ mController.sendMessage(CMD_ON_QUIT);
+ }
+
class DefaultState extends State {
@Override
public void exit() {
diff --git a/core/java/android/net/DnsPinger.java b/core/java/android/net/DnsPinger.java
index 11acabe..66f0fd0 100644
--- a/core/java/android/net/DnsPinger.java
+++ b/core/java/android/net/DnsPinger.java
@@ -295,8 +295,8 @@ public final class DnsPinger extends Handler {
}
private InetAddress getDefaultDns() {
- String dns = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.DEFAULT_DNS_SERVER);
+ String dns = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.DEFAULT_DNS_SERVER);
if (dns == null || dns.length() == 0) {
dns = mContext.getResources().getString(
com.android.internal.R.string.config_default_dns_server);
diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java
index ccd96ff..39440c2 100644
--- a/core/java/android/net/DummyDataStateTracker.java
+++ b/core/java/android/net/DummyDataStateTracker.java
@@ -119,6 +119,10 @@ public class DummyDataStateTracker implements NetworkStateTracker {
return true;
}
+ public void captivePortalCheckComplete() {
+ // 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 0cc78c9..3a06dc0 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -99,6 +99,10 @@ public class EthernetDataTracker implements NetworkStateTracker {
public void limitReached(String limitName, String iface) {
// Ignored.
}
+
+ public void interfaceClassDataActivityChanged(String label, boolean active) {
+ // Ignored.
+ }
}
private EthernetDataTracker() {
@@ -230,6 +234,10 @@ public class EthernetDataTracker implements NetworkStateTracker {
mNetworkInfo.setExtraInfo(mHwAddr);
}
}
+
+ // if a DHCP client had previously been started for this interface, then stop it
+ NetworkUtils.stopDhcp(mIface);
+
reconnect();
break;
}
@@ -266,6 +274,11 @@ public class EthernetDataTracker implements NetworkStateTracker {
return mLinkUp;
}
+ @Override
+ public void captivePortalCheckComplete() {
+ // 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 92aeff2..056fa03 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -26,6 +26,7 @@ import android.os.ParcelFileDescriptor;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
/**
* Interface that answers queries about, and allows changing, the
@@ -118,7 +119,11 @@ interface IConnectivityManager
ParcelFileDescriptor establishVpn(in VpnConfig config);
- void startLegacyVpn(in VpnConfig config, in String[] racoon, in String[] mtpd);
+ void startLegacyVpn(in VpnProfile profile);
LegacyVpnInfo getLegacyVpnInfo();
+
+ boolean updateLockdownVpn();
+
+ void captivePortalCheckComplete(in NetworkInfo info);
}
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index a97f203..6f4dd5f 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -62,4 +62,11 @@ interface INetworkManagementEventObserver {
*/
void limitReached(String limitName, String iface);
+ /**
+ * Interface data activity status is changed.
+ *
+ * @param iface The interface.
+ * @param active True if the interface is actively transmitting data, false if it is idle.
+ */
+ void interfaceClassDataActivityChanged(String label, boolean active);
}
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 3250ae7..df6057e 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -30,9 +30,9 @@ import android.net.NetworkTemplate;
interface INetworkPolicyManager {
/** Control UID policies. */
- void setAppPolicy(int appId, int policy);
- int getAppPolicy(int appId);
- int[] getAppsWithPolicy(int policy);
+ void setUidPolicy(int uid, int policy);
+ int getUidPolicy(int uid);
+ int[] getUidsWithPolicy(int policy);
boolean isUidForeground(int uid);
diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java
index 3ee8a80..14a8094 100644
--- a/core/java/android/net/LocalSocket.java
+++ b/core/java/android/net/LocalSocket.java
@@ -16,6 +16,7 @@
package android.net;
+import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
@@ -26,7 +27,7 @@ import java.net.SocketOptions;
* Creates a (non-server) socket in the UNIX-domain namespace. The interface
* here is not entirely unlike that of java.net.Socket
*/
-public class LocalSocket {
+public class LocalSocket implements Closeable {
private LocalSocketImpl impl;
private volatile boolean implCreated;
@@ -42,6 +43,15 @@ public class LocalSocket {
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));
+ isBound = true;
+ isConnected = true;
+ }
/**
* for use with AndroidServerSocket
@@ -158,6 +168,7 @@ public class LocalSocket {
*
* @throws IOException
*/
+ @Override
public void close() throws IOException {
implCreateIfNeeded();
impl.close();
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index d59fa6a..b35d61c 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -381,6 +381,11 @@ public class MobileDataStateTracker implements NetworkStateTracker {
return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED);
}
+ @Override
+ public void captivePortalCheckComplete() {
+ // 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/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 0bc6b58..0b23cb7 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -79,7 +79,9 @@ public class NetworkInfo implements Parcelable {
/** Access to this network is blocked. */
BLOCKED,
/** Link has poor connectivity. */
- VERIFYING_POOR_LINK
+ VERIFYING_POOR_LINK,
+ /** Checking if network is a captive portal */
+ CAPTIVE_PORTAL_CHECK,
}
/**
@@ -97,6 +99,7 @@ public class NetworkInfo implements Parcelable {
stateMap.put(DetailedState.AUTHENTICATING, State.CONNECTING);
stateMap.put(DetailedState.OBTAINING_IPADDR, State.CONNECTING);
stateMap.put(DetailedState.VERIFYING_POOR_LINK, State.CONNECTING);
+ stateMap.put(DetailedState.CAPTIVE_PORTAL_CHECK, State.CONNECTING);
stateMap.put(DetailedState.CONNECTED, State.CONNECTED);
stateMap.put(DetailedState.SUSPENDED, State.SUSPENDED);
stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING);
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 07bfd4b..2cd1f9b 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -26,6 +26,7 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.text.format.Time;
import com.google.android.collect.Sets;
@@ -72,29 +73,29 @@ public class NetworkPolicyManager {
}
/**
- * Set policy flags for specific application.
+ * Set policy flags for specific UID.
*
* @param policy {@link #POLICY_NONE} or combination of flags like
* {@link #POLICY_REJECT_METERED_BACKGROUND}.
*/
- public void setAppPolicy(int appId, int policy) {
+ public void setUidPolicy(int uid, int policy) {
try {
- mService.setAppPolicy(appId, policy);
+ mService.setUidPolicy(uid, policy);
} catch (RemoteException e) {
}
}
- public int getAppPolicy(int appId) {
+ public int getUidPolicy(int uid) {
try {
- return mService.getAppPolicy(appId);
+ return mService.getUidPolicy(uid);
} catch (RemoteException e) {
return POLICY_NONE;
}
}
- public int[] getAppsWithPolicy(int policy) {
+ public int[] getUidsWithPolicy(int policy) {
try {
- return mService.getAppsWithPolicy(policy);
+ return mService.getUidsWithPolicy(policy);
} catch (RemoteException e) {
return new int[0];
}
@@ -236,8 +237,7 @@ public class NetworkPolicyManager {
@Deprecated
public static boolean isUidValidForPolicy(Context context, int uid) {
// first, quick-reject non-applications
- if (uid < android.os.Process.FIRST_APPLICATION_UID
- || uid > android.os.Process.LAST_APPLICATION_UID) {
+ if (!UserHandle.isApp(uid)) {
return false;
}
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 0d6dcd6..0a0c1e0 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -41,12 +41,6 @@ public interface NetworkStateTracker {
* -------------------------------------------------------------
*/
- // Share the event space with ConnectivityService (which we can't see, but
- // must send events to). If you change these, change ConnectivityService
- // too.
- static final int MIN_NETWORK_STATE_TRACKER_EVENT = 1;
- static final int MAX_NETWORK_STATE_TRACKER_EVENT = 100;
-
/**
* The network state has changed and the NetworkInfo object
* contains the new state.
@@ -129,6 +123,11 @@ public interface NetworkStateTracker {
public boolean reconnect();
/**
+ * Ready to switch on to the network after captive portal check
+ */
+ public void captivePortalCheckComplete();
+
+ /**
* 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/NetworkStats.java b/core/java/android/net/NetworkStats.java
index fb7a4f8..446bbf0 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -21,6 +21,7 @@ import android.os.Parcelable;
import android.os.SystemClock;
import android.util.SparseBooleanArray;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Objects;
import java.io.CharArrayWriter;
@@ -608,13 +609,13 @@ public class NetworkStats implements Parcelable {
* Return all rows except those attributed to the requested UID; doesn't
* mutate the original structure.
*/
- public NetworkStats withoutUid(int uid) {
+ public NetworkStats withoutUids(int[] uids) {
final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
Entry entry = new Entry();
for (int i = 0; i < size; i++) {
entry = getValues(i, entry);
- if (entry.uid != uid) {
+ if (!ArrayUtils.contains(uids, entry.uid)) {
stats.addValues(entry);
}
}
diff --git a/core/java/android/net/ParseException.java b/core/java/android/net/ParseException.java
index 000fa68..68b209b 100644
--- a/core/java/android/net/ParseException.java
+++ b/core/java/android/net/ParseException.java
@@ -17,10 +17,9 @@
package android.net;
/**
- *
- *
- * When WebAddress Parser Fails, this exception is thrown
+ * Thrown when parsing a URL fails.
*/
+// See non-public class {@link WebAddress}.
public class ParseException extends RuntimeException {
public String response;
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 2703f1d..846443d 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -21,6 +21,7 @@ import android.util.Log;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
+import java.net.SocketException;
import java.security.KeyManagementException;
import java.security.cert.X509Certificate;
import javax.net.SocketFactory;
@@ -300,9 +301,10 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
* null if no protocol was negotiated.
*
* @param socket a socket created by this factory.
+ * @throws IllegalArgumentException if the socket was not created by this factory.
*/
public byte[] getNpnSelectedProtocol(Socket socket) {
- return ((OpenSSLSocketImpl) socket).getNpnSelectedProtocol();
+ return castToOpenSSLSocket(socket).getNpnSelectedProtocol();
}
/**
@@ -316,6 +318,54 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
mInsecureFactory = null;
}
+ /**
+ * Enables <a href="http://tools.ietf.org/html/rfc5077#section-3.2">session ticket</a>
+ * support on the given socket.
+ *
+ * @param socket a socket created by this factory
+ * @param useSessionTickets {@code true} to enable session ticket support on this socket.
+ * @throws IllegalArgumentException if the socket was not created by this factory.
+ */
+ public void setUseSessionTickets(Socket socket, boolean useSessionTickets) {
+ castToOpenSSLSocket(socket).setUseSessionTickets(useSessionTickets);
+ }
+
+ /**
+ * Turns on <a href="http://tools.ietf.org/html/rfc6066#section-3">Server
+ * Name Indication (SNI)</a> on a given socket.
+ *
+ * @param socket a socket created by this factory.
+ * @param hostName the desired SNI hostname, null to disable.
+ * @throws IllegalArgumentException if the socket was not created by this factory.
+ */
+ public void setHostname(Socket socket, String hostName) {
+ castToOpenSSLSocket(socket).setHostname(hostName);
+ }
+
+ /**
+ * Sets this socket's SO_SNDTIMEO write timeout in milliseconds.
+ * Use 0 for no timeout.
+ * To take effect, this option must be set before the blocking method was called.
+ *
+ * @param socket a socket created by this factory.
+ * @param timeout the desired write timeout in milliseconds.
+ * @throws IllegalArgumentException if the socket was not created by this factory.
+ *
+ * @hide
+ */
+ public void setSoWriteTimeout(Socket socket, int writeTimeoutMilliseconds)
+ throws SocketException {
+ castToOpenSSLSocket(socket).setSoWriteTimeout(writeTimeoutMilliseconds);
+ }
+
+ private static OpenSSLSocketImpl castToOpenSSLSocket(Socket socket) {
+ if (!(socket instanceof OpenSSLSocketImpl)) {
+ throw new IllegalArgumentException("Socket not created by this factory: "
+ + socket);
+ }
+
+ return (OpenSSLSocketImpl) socket;
+ }
/**
* {@inheritDoc}
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 3b990e3..cc6903d 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -16,10 +16,13 @@
package android.net;
+import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Environment.UserEnvironment;
import android.util.Log;
import java.io.File;
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.Charsets;
@@ -2288,4 +2291,39 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
builder = builder.appendEncodedPath(pathSegment);
return builder.build();
}
+
+ /**
+ * If this {@link Uri} is {@code file://}, then resolve and return its
+ * canonical path. Also fixes legacy emulated storage paths so they are
+ * usable across user boundaries. Should always be called from the app
+ * process before sending elsewhere.
+ *
+ * @hide
+ */
+ public Uri getCanonicalUri() {
+ if ("file".equals(getScheme())) {
+ final String canonicalPath;
+ try {
+ canonicalPath = new File(getPath()).getCanonicalPath();
+ } catch (IOException e) {
+ return this;
+ }
+
+ if (Environment.isExternalStorageEmulated()) {
+ final String legacyPath = Environment.getLegacyExternalStorageDirectory()
+ .toString();
+
+ // Splice in user-specific path when legacy path is found
+ if (canonicalPath.startsWith(legacyPath)) {
+ return Uri.fromFile(new File(
+ Environment.getExternalStorageDirectory().toString(),
+ canonicalPath.substring(legacyPath.length() + 1)));
+ }
+ }
+
+ return Uri.fromFile(new File(canonicalPath));
+ } else {
+ return this;
+ }
+ }
}
diff --git a/core/java/android/net/arp/ArpPeer.java b/core/java/android/net/arp/ArpPeer.java
index 6ba1e7c..2013b11 100644
--- a/core/java/android/net/arp/ArpPeer.java
+++ b/core/java/android/net/arp/ArpPeer.java
@@ -16,8 +16,12 @@
package android.net.arp;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.RouteInfo;
import android.os.SystemClock;
import android.util.Log;
+
import java.io.IOException;
import java.net.InetAddress;
import java.net.Inet6Address;
@@ -35,6 +39,8 @@ import libcore.net.RawSocket;
* @hide
*/
public class ArpPeer {
+ private static final boolean DBG = false;
+ private static final String TAG = "ArpPeer";
private String mInterfaceName;
private final InetAddress mMyAddr;
private final byte[] mMyMac = new byte[6];
@@ -46,7 +52,6 @@ public class ArpPeer {
private static final int ARP_LENGTH = 28;
private static final int MAC_ADDR_LENGTH = 6;
private static final int IPV4_LENGTH = 4;
- private static final String TAG = "ArpPeer";
public ArpPeer(String interfaceName, InetAddress myAddr, String mac,
InetAddress peer) throws SocketException {
@@ -125,6 +130,41 @@ public class ArpPeer {
return null;
}
+ public static boolean doArp(String myMacAddress, LinkProperties linkProperties,
+ int timeoutMillis, int numArpPings, int minArpResponses) {
+ String interfaceName = linkProperties.getInterfaceName();
+ InetAddress inetAddress = null;
+ InetAddress gateway = null;
+ boolean success;
+
+ for (LinkAddress la : linkProperties.getLinkAddresses()) {
+ inetAddress = la.getAddress();
+ break;
+ }
+
+ for (RouteInfo route : linkProperties.getRoutes()) {
+ gateway = route.getGateway();
+ break;
+ }
+
+ try {
+ ArpPeer peer = new ArpPeer(interfaceName, inetAddress, myMacAddress, gateway);
+ int responses = 0;
+ for (int i=0; i < numArpPings; i++) {
+ if(peer.doArp(timeoutMillis) != null) responses++;
+ }
+ if (DBG) Log.d(TAG, "ARP test result: " + responses + "/" + numArpPings);
+ success = (responses >= minArpResponses);
+ peer.close();
+ } catch (SocketException se) {
+ //Consider an Arp socket creation issue as a successful Arp
+ //test to avoid any wifi connectivity issues
+ Log.e(TAG, "ARP test initiation failure: " + se);
+ success = true;
+ }
+ return success;
+ }
+
public void close() {
try {
mSocket.close();
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
index 6ad8fe3..f66075d 100644
--- a/core/java/android/net/http/CertificateChainValidator.java
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -168,7 +168,13 @@ public class CertificateChainValidator {
}
try {
- SSLParametersImpl.getDefaultTrustManager().checkServerTrusted(chain, authType);
+ X509TrustManager x509TrustManager = SSLParametersImpl.getDefaultTrustManager();
+ if (x509TrustManager instanceof TrustManagerImpl) {
+ TrustManagerImpl trustManager = (TrustManagerImpl) x509TrustManager;
+ trustManager.checkServerTrusted(chain, authType, domain);
+ } else {
+ x509TrustManager.checkServerTrusted(chain, authType);
+ }
return null; // No errors.
} catch (GeneralSecurityException e) {
if (HttpLog.LOGV) {
diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
new file mode 100644
index 0000000..64eacbc
--- /dev/null
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -0,0 +1,66 @@
+/*
+ * 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.net.http;
+
+import org.apache.harmony.xnet.provider.jsse.TrustManagerImpl;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * X509TrustManager wrapper exposing Android-added features.
+ *
+ * <p> The checkServerTrusted method allows callers to perform additional
+ * verification of certificate chains after they have been successfully
+ * verified by the platform.</p>
+ */
+public class X509TrustManagerExtensions {
+
+ TrustManagerImpl mDelegate;
+
+ /**
+ * Constructs a new X509TrustManagerExtensions wrapper.
+ *
+ * @param tm A {@link X509TrustManager} as returned by TrustManagerFactory.getInstance();
+ * @throws IllegalArgumentException If tm is an unsupported TrustManager type.
+ */
+ public X509TrustManagerExtensions(X509TrustManager tm) throws IllegalArgumentException {
+ if (tm instanceof TrustManagerImpl) {
+ mDelegate = (TrustManagerImpl) tm;
+ } else {
+ throw new IllegalArgumentException("tm is not a supported type of X509TrustManager");
+ }
+ }
+
+ /**
+ * Verifies the given certificate chain.
+ *
+ * <p>See {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)} for a
+ * description of the chain and authType parameters. The final parameter, host, should be the
+ * hostname of the server.</p>
+ *
+ * @throws CertificateException if the chain does not verify correctly.
+ * @return the properly ordered chain used for verification as a list of X509Certificates.
+ */
+ public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType,
+ String host) throws CertificateException {
+ return mDelegate.checkServerTrusted(chain, authType, host);
+ }
+}
diff --git a/core/java/android/nfc/NdefRecord.java b/core/java/android/nfc/NdefRecord.java
index ed1c5b3..2d9dae9 100644
--- a/core/java/android/nfc/NdefRecord.java
+++ b/core/java/android/nfc/NdefRecord.java
@@ -688,7 +688,8 @@ public final class NdefRecord implements Parcelable {
}
} catch (FormatException e) { }
} else if (Arrays.equals(mType, RTD_URI)) {
- return parseWktUri().normalizeScheme();
+ Uri wktUri = parseWktUri();
+ return (wktUri != null ? wktUri.normalizeScheme() : null);
}
break;
diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java
index f9b765c..f2cd232 100644
--- a/core/java/android/nfc/Tag.java
+++ b/core/java/android/nfc/Tag.java
@@ -24,6 +24,7 @@ import android.nfc.tech.Ndef;
import android.nfc.tech.NdefFormatable;
import android.nfc.tech.NfcA;
import android.nfc.tech.NfcB;
+import android.nfc.tech.NfcBarcode;
import android.nfc.tech.NfcF;
import android.nfc.tech.NfcV;
import android.nfc.tech.TagTechnology;
@@ -184,6 +185,9 @@ public final class Tag implements Parcelable {
case TagTechnology.NFC_V:
strings[i] = NfcV.class.getName();
break;
+ case TagTechnology.NFC_BARCODE:
+ strings[i] = NfcBarcode.class.getName();
+ break;
default:
throw new IllegalArgumentException("Unknown tech type " + techList[i]);
}
diff --git a/core/java/android/nfc/tech/Ndef.java b/core/java/android/nfc/tech/Ndef.java
index a31cb9c..64aa299 100644
--- a/core/java/android/nfc/tech/Ndef.java
+++ b/core/java/android/nfc/tech/Ndef.java
@@ -140,8 +140,8 @@ public final class Ndef extends BasicTagTechnology {
*
* <p>Does not cause any RF activity and does not block.
*
- * @param tag an MIFARE Classic compatible tag
- * @return MIFARE Classic object
+ * @param tag an NDEF compatible tag
+ * @return Ndef object
*/
public static Ndef get(Tag tag) {
if (!tag.hasTech(TagTechnology.NDEF)) return null;
diff --git a/core/java/android/nfc/tech/NfcBarcode.java b/core/java/android/nfc/tech/NfcBarcode.java
new file mode 100644
index 0000000..3149857
--- /dev/null
+++ b/core/java/android/nfc/tech/NfcBarcode.java
@@ -0,0 +1,102 @@
+/*
+ * 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.nfc.tech;
+
+import android.nfc.Tag;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+/**
+ * Provides access to tags containing just a barcode.
+ *
+ * <p>Acquire an {@link NfcBarcode} object using {@link #get}.
+ *
+ */
+public final class NfcBarcode extends BasicTagTechnology {
+
+ /** Kovio Tags */
+ public static final int TYPE_KOVIO = 1;
+ public static final int TYPE_UNKNOWN = -1;
+
+ /** @hide */
+ public static final String EXTRA_BARCODE_TYPE = "barcodetype";
+
+ private int mType;
+
+ /**
+ * Get an instance of {@link NfcBarcode} for the given tag.
+ *
+ * <p>Returns null if {@link NfcBarcode} was not enumerated in {@link Tag#getTechList}.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @param tag an NfcBarcode compatible tag
+ * @return NfcBarcode object
+ */
+ public static NfcBarcode get(Tag tag) {
+ if (!tag.hasTech(TagTechnology.NFC_BARCODE)) return null;
+ try {
+ return new NfcBarcode(tag);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Internal constructor, to be used by NfcAdapter
+ * @hide
+ */
+ public NfcBarcode(Tag tag) throws RemoteException {
+ super(tag, TagTechnology.NFC_BARCODE);
+ Bundle extras = tag.getTechExtras(TagTechnology.NFC_BARCODE);
+ if (extras != null) {
+ mType = extras.getInt(EXTRA_BARCODE_TYPE);
+ } else {
+ throw new NullPointerException("NfcBarcode tech extras are null.");
+ }
+ }
+
+ /**
+ * Returns the NFC Barcode tag type.
+ *
+ * <p>Currently only one of {@link #TYPE_KOVIO} or {@link #TYPE_UNKNOWN}.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return the NFC Barcode tag type
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the barcode of an NfcBarcode tag.
+ *
+ * <p>Does not cause any RF activity and does not block.
+ *
+ * @return a byte array containing the barcode
+ */
+ public byte[] getBarcode() {
+ switch (mType) {
+ case TYPE_KOVIO:
+ // For Kovio tags the barcode matches the ID
+ return mTag.getId();
+ default:
+ return null;
+ }
+ }
+}
diff --git a/core/java/android/nfc/tech/TagTechnology.java b/core/java/android/nfc/tech/TagTechnology.java
index be5cbd2..3493ea7 100644
--- a/core/java/android/nfc/tech/TagTechnology.java
+++ b/core/java/android/nfc/tech/TagTechnology.java
@@ -148,6 +148,15 @@ public interface TagTechnology extends Closeable {
public static final int MIFARE_ULTRALIGHT = 9;
/**
+ * This technology is an instance of {@link NfcBarcode}.
+ * <p>Support for this technology type is optional. If a stack doesn't support this technology
+ * type tags using it must still be discovered and present the lower level radio interface
+ * technologies in use.
+ * @hide
+ */
+ public static final int NFC_BARCODE = 10;
+
+ /**
* Get the {@link Tag} object backing this {@link TagTechnology} object.
* @return the {@link Tag} backing this {@link TagTechnology} object.
*/
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index c62715b..7b16f4d 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -115,4 +115,6 @@ public class BatteryManager {
public static final int BATTERY_PLUGGED_AC = 1;
/** Power source is a USB port. */
public static final int BATTERY_PLUGGED_USB = 2;
+ /** Power source is wireless. */
+ public static final int BATTERY_PLUGGED_WIRELESS = 4;
}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 438c536..54f2fe3 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -71,9 +71,9 @@ public abstract class BatteryStats implements Parcelable {
public static final int FULL_WIFI_LOCK = 5;
/**
- * A constant indicating a scan wifi lock timer
+ * A constant indicating a wifi scan
*/
- public static final int SCAN_WIFI_LOCK = 6;
+ public static final int WIFI_SCAN = 6;
/**
* A constant indicating a wifi multicast timer
@@ -136,7 +136,7 @@ public abstract class BatteryStats implements Parcelable {
private static final String BATTERY_DATA = "bt";
private static final String BATTERY_DISCHARGE_DATA = "dc";
private static final String BATTERY_LEVEL_DATA = "lv";
- private static final String WIFI_LOCK_DATA = "wfl";
+ private static final String WIFI_DATA = "wfl";
private static final String MISC_DATA = "m";
private static final String SCREEN_BRIGHTNESS_DATA = "br";
private static final String SIGNAL_STRENGTH_TIME_DATA = "sgt";
@@ -260,8 +260,8 @@ public abstract class BatteryStats implements Parcelable {
public abstract void noteWifiStoppedLocked();
public abstract void noteFullWifiLockAcquiredLocked();
public abstract void noteFullWifiLockReleasedLocked();
- public abstract void noteScanWifiLockAcquiredLocked();
- public abstract void noteScanWifiLockReleasedLocked();
+ public abstract void noteWifiScanStartedLocked();
+ public abstract void noteWifiScanStoppedLocked();
public abstract void noteWifiMulticastEnabledLocked();
public abstract void noteWifiMulticastDisabledLocked();
public abstract void noteAudioTurnedOnLocked();
@@ -270,20 +270,22 @@ public abstract class BatteryStats implements Parcelable {
public abstract void noteVideoTurnedOffLocked();
public abstract long getWifiRunningTime(long batteryRealtime, int which);
public abstract long getFullWifiLockTime(long batteryRealtime, int which);
- public abstract long getScanWifiLockTime(long batteryRealtime, int which);
+ public abstract long getWifiScanTime(long batteryRealtime, int which);
public abstract long getWifiMulticastTime(long batteryRealtime,
int which);
public abstract long getAudioTurnedOnTime(long batteryRealtime, int which);
public abstract long getVideoTurnedOnTime(long batteryRealtime, int which);
/**
- * Note that these must match the constants in android.os.LocalPowerManager.
+ * Note that these must match the constants in android.os.PowerManager.
+ * Also, if the user activity types change, the BatteryStatsImpl.VERSION must
+ * also be bumped.
*/
static final String[] USER_ACTIVITY_TYPES = {
- "other", "cheek", "touch", "long_touch", "touch_up", "button", "unknown"
+ "other", "button", "touch"
};
- public static final int NUM_USER_ACTIVITY_TYPES = 7;
+ public static final int NUM_USER_ACTIVITY_TYPES = 3;
public abstract void noteUserActivityLocked(int type);
public abstract boolean hasUserActivity();
@@ -453,7 +455,7 @@ public abstract class BatteryStats implements Parcelable {
public static final int STATE_PHONE_SCANNING_FLAG = 1<<27;
public static final int STATE_WIFI_RUNNING_FLAG = 1<<26;
public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<25;
- public static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<24;
+ public static final int STATE_WIFI_SCAN_FLAG = 1<<24;
public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<23;
// These are on the lower bits used for the command; if they change
// we need to write another int of data.
@@ -859,7 +861,7 @@ public abstract class BatteryStats implements Parcelable {
new BitDescription(HistoryItem.STATE_WIFI_ON_FLAG, "wifi"),
new BitDescription(HistoryItem.STATE_WIFI_RUNNING_FLAG, "wifi_running"),
new BitDescription(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG, "wifi_full_lock"),
- new BitDescription(HistoryItem.STATE_WIFI_SCAN_LOCK_FLAG, "wifi_scan_lock"),
+ new BitDescription(HistoryItem.STATE_WIFI_SCAN_FLAG, "wifi_scan"),
new BitDescription(HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG, "wifi_multicast"),
new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth"),
new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio"),
@@ -1326,15 +1328,15 @@ public abstract class BatteryStats implements Parcelable {
long rx = u.getTcpBytesReceived(which);
long tx = u.getTcpBytesSent(which);
long fullWifiLockOnTime = u.getFullWifiLockTime(batteryRealtime, which);
- long scanWifiLockOnTime = u.getScanWifiLockTime(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 (fullWifiLockOnTime != 0 || scanWifiLockOnTime != 0
+ if (fullWifiLockOnTime != 0 || wifiScanTime != 0
|| uidWifiRunningTime != 0) {
- dumpLine(pw, uid, category, WIFI_LOCK_DATA,
- fullWifiLockOnTime, scanWifiLockOnTime, uidWifiRunningTime);
+ dumpLine(pw, uid, category, WIFI_DATA,
+ fullWifiLockOnTime, wifiScanTime, uidWifiRunningTime);
}
if (u.hasUserActivity()) {
@@ -1692,7 +1694,7 @@ public abstract class BatteryStats implements Parcelable {
long tcpReceived = u.getTcpBytesReceived(which);
long tcpSent = u.getTcpBytesSent(which);
long fullWifiLockOnTime = u.getFullWifiLockTime(batteryRealtime, which);
- long scanWifiLockOnTime = u.getScanWifiLockTime(batteryRealtime, which);
+ long wifiScanTime = u.getWifiScanTime(batteryRealtime, which);
long uidWifiRunningTime = u.getWifiRunningTime(batteryRealtime, which);
if (tcpReceived != 0 || tcpSent != 0) {
@@ -1703,7 +1705,7 @@ public abstract class BatteryStats implements Parcelable {
if (u.hasUserActivity()) {
boolean hasData = false;
- for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+ for (int i=0; i<Uid.NUM_USER_ACTIVITY_TYPES; i++) {
int val = u.getUserActivityCount(i, which);
if (val != 0) {
if (!hasData) {
@@ -1723,7 +1725,7 @@ public abstract class BatteryStats implements Parcelable {
}
}
- if (fullWifiLockOnTime != 0 || scanWifiLockOnTime != 0
+ if (fullWifiLockOnTime != 0 || wifiScanTime != 0
|| uidWifiRunningTime != 0) {
sb.setLength(0);
sb.append(prefix); sb.append(" Wifi Running: ");
@@ -1731,12 +1733,12 @@ public abstract class BatteryStats implements Parcelable {
sb.append("("); sb.append(formatRatioLocked(uidWifiRunningTime,
whichBatteryRealtime)); sb.append(")\n");
sb.append(prefix); sb.append(" Full Wifi Lock: ");
- formatTimeMs(sb, fullWifiLockOnTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime,
+ formatTimeMs(sb, fullWifiLockOnTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(fullWifiLockOnTime,
whichBatteryRealtime)); sb.append(")\n");
- sb.append(prefix); sb.append(" Scan Wifi Lock: ");
- formatTimeMs(sb, scanWifiLockOnTime / 1000);
- sb.append("("); sb.append(formatRatioLocked(scanWifiLockOnTime,
+ sb.append(prefix); sb.append(" Wifi Scan: ");
+ formatTimeMs(sb, wifiScanTime / 1000);
+ sb.append("("); sb.append(formatRatioLocked(wifiScanTime,
whichBatteryRealtime)); sb.append(")");
pw.println(sb.toString());
}
@@ -2069,6 +2071,9 @@ public abstract class BatteryStats implements Parcelable {
case BatteryManager.BATTERY_PLUGGED_USB:
pw.print("usb");
break;
+ case BatteryManager.BATTERY_PLUGGED_WIRELESS:
+ pw.print("wireless");
+ break;
default:
pw.print(oldPlug);
break;
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 7b51119..16b4835 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -64,7 +64,7 @@ public class Binder implements IBinder {
public static final native int getCallingPid();
/**
- * Return the ID of the user assigned to the process that sent you the
+ * Return the Linux uid assigned to the process that sent you the
* current transaction that is being processed. This uid can be used with
* higher-level system services to determine its identity and check
* permissions. If the current thread is not currently executing an
@@ -73,31 +73,15 @@ public class Binder implements IBinder {
public static final native int getCallingUid();
/**
- * Return the original ID of the user assigned to the process that sent you the current
- * transaction that is being processed. This uid can be used with higher-level system services
- * to determine its identity and check permissions. If the current thread is not currently
- * executing an incoming transaction, then its own uid is returned.
- * <p/>
- * This value cannot be reset by calls to {@link #clearCallingIdentity()}.
- * @hide
- */
- public static final int getOrigCallingUid() {
- if (UserId.MU_ENABLED) {
- return getOrigCallingUidNative();
- } else {
- return getCallingUid();
- }
- }
-
- private static final native int getOrigCallingUidNative();
-
- /**
- * Utility function to return the user id of the calling process.
- * @return userId of the calling process, extracted from the callingUid
- * @hide
+ * Return the UserHandle assigned to the process that sent you the
+ * current transaction that is being processed. This is the user
+ * of the caller. It is distinct from {@link #getCallingUid()} in that a
+ * particular user will have multiple distinct apps running under it each
+ * with their own uid. If the current thread is not currently executing an
+ * incoming transaction, then its own UserHandle is returned.
*/
- public static final int getOrigCallingUser() {
- return UserId.getUserId(getOrigCallingUid());
+ public static final UserHandle getCallingUserHandle() {
+ return new UserHandle(UserHandle.getUserId(getCallingUid()));
}
/**
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 8ec0c69..a7f39d5 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -370,7 +370,7 @@ public class Build {
public static final int ICE_CREAM_SANDWICH_MR1 = 15;
/**
- * Android 4.1.
+ * June 2012: Android 4.1.
*
* <p>Applications targeting this or a later release will get these
* new changes in behavior:</p>
@@ -411,6 +411,26 @@ public class Build {
* </ul>
*/
public static final int JELLY_BEAN = 16;
+
+ /**
+ * Android 4.2: Moar jelly beans!
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li>Content Providers: The default value of {@code android:exported} is now
+ * {@code false}. See
+ * <a href="{@docRoot}guide/topics/manifest/provider-element.html#exported">
+ * the android:exported section</a> in the provider documentation for more details.</li>
+ * <li>{@link android.view.View#getLayoutDirection() View.getLayoutDirection()}
+ * can return different values than {@link android.view.View#LAYOUT_DIRECTION_LTR}
+ * based on the locale etc.
+ * <li> {@link android.webkit.WebView#addJavascriptInterface(Object, String)
+ * WebView.addJavascriptInterface} requires explicit annotations on methods
+ * for them to be accessible from Javascript.
+ * </ul>
+ */
+ public static final int JELLY_BEAN_MR1 = 17;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 591cd0e..c08bfeb 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -95,7 +95,7 @@ public final class Debug
* Default trace file path and file
*/
private static final String DEFAULT_TRACE_PATH_PREFIX =
- Environment.getExternalStorageDirectory().getPath() + "/";
+ Environment.getLegacyExternalStorageDirectory().getPath() + "/";
private static final String DEFAULT_TRACE_BODY = "dmtrace";
private static final String DEFAULT_TRACE_EXTENSION = ".trace";
private static final String DEFAULT_TRACE_FILE_PATH =
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 679cf1a..3315566 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -16,9 +16,10 @@
package android.os;
-import android.content.res.Resources;
import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
+import android.text.TextUtils;
import android.util.Log;
import java.io.File;
@@ -29,31 +30,146 @@ import java.io.File;
public class Environment {
private static final String TAG = "Environment";
+ private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
+ 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";
+
+ /** {@hide} */
+ public static String DIRECTORY_ANDROID = "Android";
+
private static final File ROOT_DIRECTORY
= getDirectory("ANDROID_ROOT", "/system");
private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
- private static final Object mLock = new Object();
+ private static UserEnvironment sCurrentUser;
+
+ private static final Object sLock = new Object();
- private volatile static StorageVolume mPrimaryVolume = null;
+ // @GuardedBy("sLock")
+ private static volatile StorageVolume sPrimaryVolume;
private static StorageVolume getPrimaryVolume() {
- if (mPrimaryVolume == null) {
- synchronized (mLock) {
- if (mPrimaryVolume == null) {
+ if (sPrimaryVolume == null) {
+ synchronized (sLock) {
+ if (sPrimaryVolume == null) {
try {
IMountService mountService = IMountService.Stub.asInterface(ServiceManager
.getService("mount"));
- Parcelable[] volumes = mountService.getVolumeList();
- mPrimaryVolume = (StorageVolume)volumes[0];
+ final StorageVolume[] volumes = mountService.getVolumeList();
+ sPrimaryVolume = StorageManager.getPrimaryVolume(volumes);
} catch (Exception e) {
Log.e(TAG, "couldn't talk to MountService", e);
}
}
}
}
- return mPrimaryVolume;
+ return sPrimaryVolume;
+ }
+
+ static {
+ initForCurrentUser();
+ }
+
+ /** {@hide} */
+ public static void initForCurrentUser() {
+ final int userId = UserHandle.myUserId();
+ sCurrentUser = new UserEnvironment(userId);
+
+ synchronized (sLock) {
+ sPrimaryVolume = null;
+ }
+ }
+
+ /** {@hide} */
+ 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;
+
+ 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 rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE);
+ if (TextUtils.isEmpty(rawMediaStorage)) {
+ rawMediaStorage = "/data/media";
+ }
+
+ if (!TextUtils.isEmpty(rawEmulatedStorageTarget)) {
+ // 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 mediaBase = new File(rawMediaStorage);
+
+ // /storage/emulated/0
+ mExternalStorage = buildPath(emulatedBase, rawUserId);
+ // /data/media/0
+ mMediaStorage = buildPath(mediaBase, rawUserId);
+
+ } else {
+ // Device has physical external storage; use plain paths.
+ if (TextUtils.isEmpty(rawExternalStorage)) {
+ Log.w(TAG, "EXTERNAL_STORAGE undefined; falling back to default");
+ rawExternalStorage = "/storage/sdcard0";
+ }
+
+ // /storage/sdcard0
+ mExternalStorage = new File(rawExternalStorage);
+ // /data/media
+ mMediaStorage = new File(rawMediaStorage);
+ }
+
+ mExternalStorageAndroidObb = buildPath(mExternalStorage, DIRECTORY_ANDROID, "obb");
+ mExternalStorageAndroidData = buildPath(mExternalStorage, DIRECTORY_ANDROID, "data");
+ mExternalStorageAndroidMedia = buildPath(mExternalStorage, DIRECTORY_ANDROID, "media");
+ }
+
+ public File getExternalStorageDirectory() {
+ return mExternalStorage;
+ }
+
+ public File getExternalStorageObbDirectory() {
+ return mExternalStorageAndroidObb;
+ }
+
+ public File getExternalStoragePublicDirectory(String type) {
+ return new File(mExternalStorage, type);
+ }
+
+ public File getExternalStorageAndroidDataDir() {
+ return mExternalStorageAndroidData;
+ }
+
+ public File getExternalStorageAppDataDirectory(String packageName) {
+ return new File(mExternalStorageAndroidData, packageName);
+ }
+
+ public File getExternalStorageAppMediaDirectory(String packageName) {
+ return new File(mExternalStorageAndroidMedia, packageName);
+ }
+
+ public File getExternalStorageAppObbDirectory(String packageName) {
+ return new File(mExternalStorageAndroidObb, packageName);
+ }
+
+ public File getExternalStorageAppFilesDirectory(String packageName) {
+ return new File(new File(mExternalStorageAndroidData, packageName), "files");
+ }
+
+ public File getExternalStorageAppCacheDirectory(String packageName) {
+ return new File(new File(mExternalStorageAndroidData, packageName), "cache");
+ }
+
+ public File getMediaStorageDirectory() {
+ return mMediaStorage;
+ }
}
/**
@@ -100,7 +216,19 @@ public class Environment {
* @hide
*/
public static File getMediaStorageDirectory() {
- return MEDIA_STORAGE_DIRECTORY;
+ throwIfSystem();
+ return sCurrentUser.getMediaStorageDirectory();
+ }
+
+ /**
+ * Return the system directory for a user. This is for use by system services to store
+ * files relating to the user. This directory will be automatically deleted when the user
+ * is removed.
+ *
+ * @hide
+ */
+ public static File getUserSystemDirectory(int userId) {
+ return new File(new File(getSystemSecureDirectory(), "users"), Integer.toString(userId));
}
/**
@@ -122,24 +250,7 @@ public class Environment {
private static final File SECURE_DATA_DIRECTORY
= getDirectory("ANDROID_SECURE_DATA", "/data/secure");
- /** @hide */
- private static final File MEDIA_STORAGE_DIRECTORY
- = getDirectory("MEDIA_STORAGE", "/data/media");
-
- private static final File EXTERNAL_STORAGE_DIRECTORY
- = getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0");
-
- private static final File EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY = new File(new File(
- getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "data");
-
- private static final File EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY = new File(new File(
- getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "media");
-
- private static final File EXTERNAL_STORAGE_ANDROID_OBB_DIRECTORY = new File(new File(
- getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "obb");
-
- private static final File DOWNLOAD_CACHE_DIRECTORY
- = getDirectory("DOWNLOAD_CACHE", "/cache");
+ private static final File DOWNLOAD_CACHE_DIRECTORY = getDirectory("DOWNLOAD_CACHE", "/cache");
/**
* Gets the Android data directory.
@@ -187,7 +298,30 @@ public class Environment {
* @see #isExternalStorageRemovable()
*/
public static File getExternalStorageDirectory() {
- return EXTERNAL_STORAGE_DIRECTORY;
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageDirectory();
+ }
+
+ /** {@hide} */
+ public static File getLegacyExternalStorageDirectory() {
+ return new File(System.getenv(ENV_EXTERNAL_STORAGE));
+ }
+
+ /** {@hide} */
+ public static File getLegacyExternalStorageObbDirectory() {
+ return buildPath(getLegacyExternalStorageDirectory(), DIRECTORY_ANDROID, "obb");
+ }
+
+ /** {@hide} */
+ public static File getEmulatedStorageSource(int userId) {
+ // /mnt/shell/emulated/0
+ return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), String.valueOf(userId));
+ }
+
+ /** {@hide} */
+ public static File getEmulatedStorageObbSource() {
+ // /mnt/shell/emulated/obb
+ return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), "obb");
}
/**
@@ -307,7 +441,8 @@ public class Environment {
* using it such as with {@link File#mkdirs File.mkdirs()}.
*/
public static File getExternalStoragePublicDirectory(String type) {
- return new File(getExternalStorageDirectory(), type);
+ throwIfSystem();
+ return sCurrentUser.getExternalStoragePublicDirectory(type);
}
/**
@@ -315,7 +450,8 @@ public class Environment {
* @hide
*/
public static File getExternalStorageAndroidDataDir() {
- return EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY;
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageAndroidDataDir();
}
/**
@@ -323,7 +459,8 @@ public class Environment {
* @hide
*/
public static File getExternalStorageAppDataDirectory(String packageName) {
- return new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, packageName);
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageAppDataDirectory(packageName);
}
/**
@@ -331,7 +468,8 @@ public class Environment {
* @hide
*/
public static File getExternalStorageAppMediaDirectory(String packageName) {
- return new File(EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY, packageName);
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageAppMediaDirectory(packageName);
}
/**
@@ -339,7 +477,8 @@ public class Environment {
* @hide
*/
public static File getExternalStorageAppObbDirectory(String packageName) {
- return new File(EXTERNAL_STORAGE_ANDROID_OBB_DIRECTORY, packageName);
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageAppObbDirectory(packageName);
}
/**
@@ -347,17 +486,17 @@ public class Environment {
* @hide
*/
public static File getExternalStorageAppFilesDirectory(String packageName) {
- return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY,
- packageName), "files");
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageAppFilesDirectory(packageName);
}
-
+
/**
* Generates the path to an application's cache.
* @hide
*/
public static File getExternalStorageAppCacheDirectory(String packageName) {
- return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY,
- packageName), "cache");
+ throwIfSystem();
+ return sCurrentUser.getExternalStorageAppCacheDirectory(packageName);
}
/**
@@ -430,9 +569,10 @@ public class Environment {
try {
IMountService mountService = IMountService.Stub.asInterface(ServiceManager
.getService("mount"));
- return mountService.getVolumeState(getExternalStorageDirectory()
- .toString());
- } catch (Exception rex) {
+ 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;
}
}
@@ -446,8 +586,8 @@ public class Environment {
* <p>See {@link #getExternalStorageDirectory()} for more information.
*/
public static boolean isExternalStorageRemovable() {
- StorageVolume volume = getPrimaryVolume();
- return (volume != null && volume.isRemovable());
+ final StorageVolume primary = getPrimaryVolume();
+ return (primary != null && primary.isRemovable());
}
/**
@@ -464,12 +604,30 @@ public class Environment {
* android.content.ComponentName, boolean)} for additional details.
*/
public static boolean isExternalStorageEmulated() {
- StorageVolume volume = getPrimaryVolume();
- return (volume != null && volume.isEmulated());
+ final StorageVolume primary = getPrimaryVolume();
+ return (primary != null && primary.isEmulated());
}
static File getDirectory(String variableName, String defaultPath) {
String path = System.getenv(variableName);
return path == null ? new File(defaultPath) : new File(path);
}
+
+ private static void throwIfSystem() {
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ Log.wtf(TAG, "Static storage paths aren't available from AID_SYSTEM", new Throwable());
+ }
+ }
+
+ private static File buildPath(File base, String... segments) {
+ File cur = base;
+ for (String segment : segments) {
+ if (cur == null) {
+ cur = new File(segment);
+ } else {
+ cur = new File(cur, segment);
+ }
+ }
+ return cur;
+ }
}
diff --git a/core/java/android/os/FactoryTest.java b/core/java/android/os/FactoryTest.java
new file mode 100644
index 0000000..ec99697
--- /dev/null
+++ b/core/java/android/os/FactoryTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.os;
+
+/**
+ * Provides support for in-place factory test functions.
+ *
+ * This class provides a few properties that alter the normal operation of the system
+ * during factory testing.
+ *
+ * {@hide}
+ */
+public final class FactoryTest {
+ /**
+ * When true, long-press on power should immediately cause the device to
+ * shut down, without prompting the user.
+ */
+ public static boolean isLongPressOnPowerOffEnabled() {
+ return SystemProperties.getInt("factory.long_press_power_off", 0) != 0;
+ }
+}
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index b892c81..94de448 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -101,36 +101,88 @@ public class Handler {
}
/**
- * Default constructor associates this handler with the queue for the
+ * Default constructor associates this handler with the {@link Looper} for the
* current thread.
*
- * If there isn't one, this handler won't be able to receive messages.
+ * If this thread does not have a looper, this handler won't be able to receive messages
+ * so an exception is thrown.
*/
public Handler() {
- if (FIND_POTENTIAL_LEAKS) {
- final Class<? extends Handler> klass = getClass();
- if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
- (klass.getModifiers() & Modifier.STATIC) == 0) {
- Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
- klass.getCanonicalName());
- }
- }
-
- mLooper = Looper.myLooper();
- if (mLooper == null) {
- throw new RuntimeException(
- "Can't create handler inside thread that has not called Looper.prepare()");
- }
- mQueue = mLooper.mQueue;
- mCallback = null;
+ this(null, false);
}
/**
- * Constructor associates this handler with the queue for the
+ * Constructor associates this handler with the {@link Looper} for the
* current thread and takes a callback interface in which you can handle
* messages.
+ *
+ * If this thread does not have a looper, this handler won't be able to receive messages
+ * so an exception is thrown.
+ *
+ * @param callback The callback interface in which to handle messages, or null.
*/
public Handler(Callback callback) {
+ this(callback, false);
+ }
+
+ /**
+ * Use the provided {@link Looper} instead of the default one.
+ *
+ * @param looper The looper, must not be null.
+ */
+ public Handler(Looper looper) {
+ this(looper, null, false);
+ }
+
+ /**
+ * Use the provided {@link Looper} instead of the default one and take a callback
+ * interface in which to handle messages.
+ *
+ * @param looper The looper, must not be null.
+ * @param callback The callback interface in which to handle messages, or null.
+ */
+ public Handler(Looper looper, Callback callback) {
+ this(looper, callback, false);
+ }
+
+ /**
+ * Use the {@link Looper} for the current thread
+ * and set whether the handler should be asynchronous.
+ *
+ * Handlers are synchronous by default unless this constructor is used to make
+ * one that is strictly asynchronous.
+ *
+ * Asynchronous messages represent interrupts or events that do not require global ordering
+ * with represent to synchronous messages. Asynchronous messages are not subject to
+ * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
+ *
+ * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
+ * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
+ *
+ * @hide
+ */
+ public Handler(boolean async) {
+ this(null, async);
+ }
+
+ /**
+ * Use the {@link Looper} for the current thread with the specified callback interface
+ * and set whether the handler should be asynchronous.
+ *
+ * Handlers are synchronous by default unless this constructor is used to make
+ * one that is strictly asynchronous.
+ *
+ * Asynchronous messages represent interrupts or events that do not require global ordering
+ * with represent to synchronous messages. Asynchronous messages are not subject to
+ * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
+ *
+ * @param callback The callback interface in which to handle messages, or null.
+ * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
+ * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
+ *
+ * @hide
+ */
+ public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
@@ -147,25 +199,33 @@ public class Handler {
}
mQueue = mLooper.mQueue;
mCallback = callback;
+ mAsynchronous = async;
}
/**
- * Use the provided queue instead of the default one.
- */
- public Handler(Looper looper) {
- mLooper = looper;
- mQueue = looper.mQueue;
- mCallback = null;
- }
-
- /**
- * Use the provided queue instead of the default one and take a callback
- * interface in which to handle messages.
+ * Use the provided {@link Looper} instead of the default one and take a callback
+ * interface in which to handle messages. Also set whether the handler
+ * should be asynchronous.
+ *
+ * Handlers are synchronous by default unless this constructor is used to make
+ * one that is strictly asynchronous.
+ *
+ * Asynchronous messages represent interrupts or events that do not require global ordering
+ * with represent to synchronous messages. Asynchronous messages are not subject to
+ * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
+ *
+ * @param looper The looper, must not be null.
+ * @param callback The callback interface in which to handle messages, or null.
+ * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
+ * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
+ *
+ * @hide
*/
- public Handler(Looper looper, Callback callback) {
+ public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
+ mAsynchronous = async;
}
/**
@@ -352,6 +412,58 @@ public class Handler {
}
/**
+ * Runs the specified task synchronously.
+ *
+ * If the current thread is the same as the handler thread, then the runnable
+ * runs immediately without being enqueued. Otherwise, posts the runnable
+ * to the handler and waits for it to complete before returning.
+ *
+ * This method is dangerous! Improper use can result in deadlocks.
+ * Never call this method while any locks are held or use it in a
+ * possibly re-entrant manner.
+ *
+ * This method is occasionally useful in situations where a background thread
+ * must synchronously await completion of a task that must run on the
+ * handler's thread. However, this problem is often a symptom of bad design.
+ * Consider improving the design (if possible) before resorting to this method.
+ *
+ * One example of where you might want to use this method is when you just
+ * set up a Handler thread and need to perform some initialization steps on
+ * it before continuing execution.
+ *
+ * If timeout occurs then this method returns <code>false</code> but the runnable
+ * will remain posted on the handler and may already be in progress or
+ * complete at a later time.
+ *
+ * @param r The Runnable that will be executed synchronously.
+ * @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
+ *
+ * @return Returns true if the Runnable was successfully executed.
+ * Returns false on failure, usually because the
+ * looper processing the message queue is exiting.
+ *
+ * @hide This method is prone to abuse and should probably not be in the API.
+ * If we ever do make it part of the API, we might want to rename it to something
+ * less funny like runUnsafe().
+ */
+ public final boolean runWithScissors(final Runnable r, long timeout) {
+ if (r == null) {
+ throw new IllegalArgumentException("runnable must not be null");
+ }
+ if (timeout < 0) {
+ throw new IllegalArgumentException("timeout must be non-negative");
+ }
+
+ if (Looper.myLooper() == mLooper) {
+ r.run();
+ return true;
+ }
+
+ BlockingRunnable br = new BlockingRunnable(r);
+ return br.postAndWait(this, timeout);
+ }
+
+ /**
* Remove any pending posts of Runnable r that are in the message queue.
*/
public final void removeCallbacks(Runnable r)
@@ -464,20 +576,15 @@ public class Handler {
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
- public boolean sendMessageAtTime(Message msg, long uptimeMillis)
- {
- boolean sent = false;
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
- if (queue != null) {
- msg.target = this;
- sent = queue.enqueueMessage(msg, uptimeMillis);
- }
- else {
+ if (queue == null) {
RuntimeException e = new RuntimeException(
- this + " sendMessageAtTime() called with no mQueue");
+ this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
+ return false;
}
- return sent;
+ return enqueueMessage(queue, msg, uptimeMillis);
}
/**
@@ -492,20 +599,23 @@ public class Handler {
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
- public final boolean sendMessageAtFrontOfQueue(Message msg)
- {
- boolean sent = false;
+ public final boolean sendMessageAtFrontOfQueue(Message msg) {
MessageQueue queue = mQueue;
- if (queue != null) {
- msg.target = this;
- sent = queue.enqueueMessage(msg, 0);
- }
- else {
+ if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
+ return false;
+ }
+ return enqueueMessage(queue, msg, 0);
+ }
+
+ private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
+ msg.target = this;
+ if (mAsynchronous) {
+ msg.setAsynchronous(true);
}
- return sent;
+ return queue.enqueueMessage(msg, uptimeMillis);
}
/**
@@ -618,5 +728,57 @@ public class Handler {
final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;
+ final boolean mAsynchronous;
IMessenger mMessenger;
+
+ private static final class BlockingRunnable implements Runnable {
+ private final Runnable mTask;
+ private boolean mDone;
+
+ public BlockingRunnable(Runnable task) {
+ mTask = task;
+ }
+
+ @Override
+ public void run() {
+ try {
+ mTask.run();
+ } finally {
+ synchronized (this) {
+ mDone = true;
+ notifyAll();
+ }
+ }
+ }
+
+ public boolean postAndWait(Handler handler, long timeout) {
+ if (!handler.post(this)) {
+ return false;
+ }
+
+ synchronized (this) {
+ if (timeout > 0) {
+ final long expirationTime = SystemClock.uptimeMillis() + timeout;
+ while (!mDone) {
+ long delay = expirationTime - SystemClock.uptimeMillis();
+ if (delay <= 0) {
+ return false; // timeout
+ }
+ try {
+ wait(delay);
+ } catch (InterruptedException ex) {
+ }
+ }
+ } else {
+ while (!mDone) {
+ try {
+ wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ }
+ return true;
+ }
+ }
}
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index e7ea355..2179fa1 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -152,6 +152,16 @@ interface INetworkManagementService
boolean isTetheringStarted();
/**
+ * Start bluetooth reverse tethering services
+ */
+ void startReverseTethering(in String iface);
+
+ /**
+ * Stop currently running bluetooth reserse tethering services
+ */
+ void stopReverseTethering();
+
+ /**
* Tethers the specified interface
*/
void tetherInterface(String iface);
@@ -218,17 +228,17 @@ interface INetworkManagementService
/**
* Start Wifi Access Point
*/
- void startAccessPoint(in WifiConfiguration wifiConfig, String wlanIface, String softapIface);
+ void startAccessPoint(in WifiConfiguration wifiConfig, String iface);
/**
* Stop Wifi Access Point
*/
- void stopAccessPoint(String wlanIface);
+ void stopAccessPoint(String iface);
/**
* Set Access Point config
*/
- void setAccessPoint(in WifiConfiguration wifiConfig, String wlanIface, String softapIface);
+ void setAccessPoint(in WifiConfiguration wifiConfig, String iface);
/**
** DATA USAGE RELATED
@@ -313,6 +323,27 @@ interface INetworkManagementService
int getInterfaceTxThrottle(String iface);
/**
+ * Sets idletimer for an interface.
+ *
+ * This either initializes a new idletimer or increases its
+ * reference-counting if an idletimer already exists for given
+ * {@code iface}.
+ *
+ * {@code label} usually represents the network type of {@code iface}.
+ * Caller should ensure that {@code label} for an {@code iface} remains the
+ * same for all calls to addIdleTimer.
+ *
+ * Every {@code addIdleTimer} should be paired with a
+ * {@link removeIdleTimer} to cleanup when the network disconnects.
+ */
+ void addIdleTimer(String iface, int timeout, String label);
+
+ /**
+ * Removes idletimer for an interface.
+ */
+ void removeIdleTimer(String iface);
+
+ /**
* Sets the name of the default interface in the DNS resolver.
*/
void setDefaultInterfaceForDns(String iface);
@@ -331,4 +362,11 @@ interface INetworkManagementService
* Flush the DNS cache associated with the specified interface.
*/
void flushInterfaceDnsCache(String iface);
+
+ void setFirewallEnabled(boolean enabled);
+ boolean isFirewallEnabled();
+ void setFirewallInterfaceRule(String iface, boolean allow);
+ void setFirewallEgressSourceRule(String addr, boolean allow);
+ void setFirewallEgressDestRule(String addr, int port, boolean allow);
+ void setFirewallUidRule(int uid, boolean allow);
}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 270e9be..557d3f3 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -23,27 +23,31 @@ import android.os.WorkSource;
interface IPowerManager
{
- // WARNING: changes in acquireWakeLock() signature must be reflected in IPowerManager.cpp/h
- void acquireWakeLock(int flags, IBinder lock, String tag, in WorkSource ws);
- void updateWakeLockWorkSource(IBinder lock, in WorkSource ws);
- void goToSleep(long time);
- void goToSleepWithReason(long time, int reason);
- // WARNING: changes in releaseWakeLock() signature must be reflected in IPowerManager.cpp/h
+ // WARNING: The first two methods must remain the first two 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 releaseWakeLock(IBinder lock, int flags);
- void userActivity(long when, boolean noChangeLights);
- void userActivityWithForce(long when, boolean noChangeLights, boolean force);
- void clearUserActivityTimeout(long now, long timeout);
- void setPokeLock(int pokey, IBinder lock, String tag);
- int getSupportedWakeLockFlags();
- void setStayOnSetting(int val);
- void setMaximumScreenOffTimeount(int timeMs);
- void preventScreenOn(boolean prevent);
+
+ void updateWakeLockWorkSource(IBinder lock, in WorkSource ws);
+ boolean isWakeLockLevelSupported(int level);
+
+ void userActivity(long time, int event, int flags);
+ void wakeUp(long time);
+ void goToSleep(long time, int reason);
+ void nap(long time);
+
boolean isScreenOn();
void reboot(String reason);
void crash(String message);
- // sets the brightness of the backlights (screen, keyboard, button) 0-255
- void setBacklightBrightness(int brightness);
+ void setStayOnSetting(int val);
+ void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs);
+
+ // temporarily overrides the screen brightness settings to allow the user to
+ // see the effect of a settings change without applying it immediately
+ void setTemporaryScreenBrightnessSettingOverride(int brightness);
+ void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float adj);
+
+ // sets the attention light (used by phone app only)
void setAttentionLight(boolean on, int color);
- void setAutoBrightnessAdjustment(float adj);
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
new file mode 100644
index 0000000..ec02ae0
--- /dev/null
+++ b/core/java/android/os/IUserManager.aidl
@@ -0,0 +1,40 @@
+/*
+**
+** Copyright 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.os;
+
+import android.os.ParcelFileDescriptor;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+
+/**
+ * {@hide}
+ */
+interface IUserManager {
+ UserInfo createUser(in String name, int flags);
+ boolean removeUser(int userHandle);
+ void setUserName(int userHandle, String name);
+ void setUserIcon(int userHandle, in Bitmap icon);
+ Bitmap getUserIcon(int userHandle);
+ List<UserInfo> getUsers(boolean excludeDying);
+ UserInfo getUserInfo(int userHandle);
+ void setGuestEnabled(boolean enable);
+ boolean isGuestEnabled();
+ void wipeUser(int userHandle);
+ int getUserSerialNumber(int userHandle);
+ int getUserHandle(int userSerialNumber);
+}
diff --git a/core/java/android/os/LocalPowerManager.java b/core/java/android/os/LocalPowerManager.java
deleted file mode 100644
index 52df1f8..0000000
--- a/core/java/android/os/LocalPowerManager.java
+++ /dev/null
@@ -1,48 +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.os;
-
-/** @hide */
-public interface LocalPowerManager {
- // Note: be sure to update BatteryStats if adding or modifying event constants.
-
- public static final int OTHER_EVENT = 0;
- public static final int BUTTON_EVENT = 1;
- public static final int TOUCH_EVENT = 2;
-
- public static final int POKE_LOCK_IGNORE_TOUCH_EVENTS = 0x1;
-
- public static final int POKE_LOCK_SHORT_TIMEOUT = 0x2;
- public static final int POKE_LOCK_MEDIUM_TIMEOUT = 0x4;
- public static final int POKE_LOCK_TIMEOUT_MASK = 0x6;
-
- void goToSleep(long time);
-
- // notify power manager when keyboard is opened/closed
- void setKeyboardVisibility(boolean visible);
-
- // when the keyguard is up, it manages the power state, and userActivity doesn't do anything.
- void enableUserActivity(boolean enabled);
-
- // the same as the method on PowerManager
- void userActivity(long time, boolean noChangeLights, int eventType);
-
- boolean isScreenOn();
-
- void setScreenBrightnessOverride(int brightness);
- void setButtonBrightnessOverride(int brightness);
-}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 903c8b3..ae50ddb 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -16,22 +16,25 @@
package android.os;
+import android.content.Context;
import android.util.Log;
/**
- * This class gives you control of the power state of the device.
- *
- * <p><b>Device battery life will be significantly affected by the use of this API.</b> Do not
- * acquire WakeLocks unless you really need them, use the minimum levels possible, and be sure
- * to release it as soon as you can.
- *
- * <p>You can obtain an instance of this class by calling
+ * This class gives you control of the power state of the device.
+ *
+ * <p>
+ * <b>Device battery life will be significantly affected by the use of this API.</b>
+ * Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
+ * possible, and be sure to release them as soon as possible.
+ * </p><p>
+ * You can obtain an instance of this class by calling
* {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
- *
- * <p>The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}. This will
- * create a {@link PowerManager.WakeLock} object. You can then use methods on this object to
- * control the power state of the device. In practice it's quite simple:
- *
+ * </p><p>
+ * The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}.
+ * This will create a {@link PowerManager.WakeLock} object. You can then use methods
+ * on the wake lock object to control the power state of the device.
+ * </p><p>
+ * In practice it's quite simple:
* {@samplecode
* PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
* PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
@@ -39,11 +42,11 @@ import android.util.Log;
* ..screen will stay on during this section..
* wl.release();
* }
- *
- * <p>The following flags are defined, with varying effects on system power. <i>These flags are
- * mutually exclusive - you may only specify one of them.</i>
- * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ * </p><p>
+ * The following wake lock levels are defined, with varying effects on system power.
+ * <i>These levels are mutually exclusive - you may only specify one of them.</i>
*
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
* <thead>
* <tr><th>Flag Value</th>
* <th>CPU</th> <th>Screen</th> <th>Keyboard</th></tr>
@@ -67,15 +70,16 @@ import android.util.Log;
* </tr>
* </tbody>
* </table>
- *
- * <p>*<i>If you hold a partial wakelock, the CPU will continue to run, irrespective of any timers
- * and even after the user presses the power button. In all other wakelocks, the CPU will run, but
- * the user can still put the device to sleep using the power button.</i>
- *
- * <p>In addition, you can add two more flags, which affect behavior of the screen only. <i>These
- * flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i>
- * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ * </p><p>
+ * *<i>If you hold a partial wake lock, the CPU will continue to run, regardless of any
+ * display timeouts or the state of the screen and even after the user presses the power button.
+ * In all other wake locks, the CPU will run, but the user can still put the device to sleep
+ * using the power button.</i>
+ * </p><p>
+ * In addition, you can add two more flags, which affect behavior of the screen only.
+ * <i>These flags have no effect when combined with a {@link #PARTIAL_WAKE_LOCK}.</i>
*
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
* <thead>
* <tr><th>Flag Value</th> <th>Description</th></tr>
* </thead>
@@ -96,113 +100,132 @@ import android.util.Log;
* </tr>
* </tbody>
* </table>
- *
+ * </p><p>
* Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
* permission in an {@code &lt;uses-permission&gt;} element of the application's manifest.
+ * </p>
*/
-public class PowerManager
-{
+public final class PowerManager {
private static final String TAG = "PowerManager";
-
- /**
- * These internal values define the underlying power elements that we might
- * want to control individually. Eventually we'd like to expose them.
+
+ /* NOTE: Wake lock levels were previously defined as a bit field, except that only a few
+ * combinations were actually supported so the bit field was removed. This explains
+ * why the numbering scheme is so odd. If adding a new wake lock level, any unused
+ * value can be used.
*/
- private static final int WAKE_BIT_CPU_STRONG = 1;
- private static final int WAKE_BIT_CPU_WEAK = 2;
- private static final int WAKE_BIT_SCREEN_DIM = 4;
- private static final int WAKE_BIT_SCREEN_BRIGHT = 8;
- private static final int WAKE_BIT_KEYBOARD_BRIGHT = 16;
- private static final int WAKE_BIT_PROXIMITY_SCREEN_OFF = 32;
-
- private static final int LOCK_MASK = WAKE_BIT_CPU_STRONG
- | WAKE_BIT_CPU_WEAK
- | WAKE_BIT_SCREEN_DIM
- | WAKE_BIT_SCREEN_BRIGHT
- | WAKE_BIT_KEYBOARD_BRIGHT
- | WAKE_BIT_PROXIMITY_SCREEN_OFF;
/**
- * Wake lock that ensures that the CPU is running. The screen might
- * not be on.
+ * Wake lock level: Ensures that the CPU is running; the screen and keyboard
+ * backlight will be allowed to go off.
+ * <p>
+ * If the user presses the power button, then the screen will be turned off
+ * but the CPU will be kept on until all partial wake locks have been released.
+ * </p>
*/
- public static final int PARTIAL_WAKE_LOCK = WAKE_BIT_CPU_STRONG;
+ public static final int PARTIAL_WAKE_LOCK = 0x00000001;
/**
- * Wake lock that ensures that the screen and keyboard are on at
- * full brightness.
+ * Wake lock level: Ensures that the screen is on (but may be dimmed);
+ * the keyboard backlight will be allowed to go off.
+ * <p>
+ * If the user presses the power button, then the {@link #SCREEN_DIM_WAKE_LOCK} will be
+ * implicitly released by the system, causing both the screen and the CPU to be turned off.
+ * Contrast with {@link #PARTIAL_WAKE_LOCK}.
+ * </p>
*
- * <p class="note">Most applications should strongly consider using
- * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON}.
- * This window flag will be correctly managed by the platform
- * as the user moves between applications and doesn't require a special permission.</p>
+ * @deprecated Most applications should use
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead
+ * of this type of wake lock, as it will be correctly managed by the platform
+ * as the user moves between applications and doesn't require a special permission.
*/
- public static final int FULL_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT
- | WAKE_BIT_KEYBOARD_BRIGHT;
+ @Deprecated
+ public static final int SCREEN_DIM_WAKE_LOCK = 0x00000006;
/**
+ * Wake lock level: Ensures that the screen is on at full brightness;
+ * the keyboard backlight will be allowed to go off.
+ * <p>
+ * If the user presses the power button, then the {@link #SCREEN_BRIGHT_WAKE_LOCK} will be
+ * implicitly released by the system, causing both the screen and the CPU to be turned off.
+ * Contrast with {@link #PARTIAL_WAKE_LOCK}.
+ * </p>
+ *
* @deprecated Most applications should use
* {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead
* of this type of wake lock, as it will be correctly managed by the platform
* as the user moves between applications and doesn't require a special permission.
- *
- * Wake lock that ensures that the screen is on at full brightness;
- * the keyboard backlight will be allowed to go off.
*/
@Deprecated
- public static final int SCREEN_BRIGHT_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT;
+ public static final int SCREEN_BRIGHT_WAKE_LOCK = 0x0000000a;
/**
- * Wake lock that ensures that the screen is on (but may be dimmed);
- * the keyboard backlight will be allowed to go off.
+ * Wake lock level: Ensures that the screen and keyboard backlight are on at
+ * full brightness.
+ * <p>
+ * If the user presses the power button, then the {@link #FULL_WAKE_LOCK} will be
+ * implicitly released by the system, causing both the screen and the CPU to be turned off.
+ * Contrast with {@link #PARTIAL_WAKE_LOCK}.
+ * </p>
+ *
+ * @deprecated Most applications should use
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead
+ * of this type of wake lock, as it will be correctly managed by the platform
+ * as the user moves between applications and doesn't require a special permission.
*/
- public static final int SCREEN_DIM_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_DIM;
+ @Deprecated
+ public static final int FULL_WAKE_LOCK = 0x0000001a;
/**
- * Wake lock that turns the screen off when the proximity sensor activates.
- * Since not all devices have proximity sensors, use
- * {@link #getSupportedWakeLockFlags() getSupportedWakeLockFlags()} to determine if
- * this wake lock mode is supported.
+ * Wake lock level: Turns the screen off when the proximity sensor activates.
+ * <p>
+ * Since not all devices have proximity sensors, use {@link #isWakeLockLevelSupported}
+ * to determine whether this wake lock level is supported.
+ * </p>
*
* {@hide}
*/
- public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = WAKE_BIT_PROXIMITY_SCREEN_OFF;
+ public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 0x00000020;
/**
- * Flag for {@link WakeLock#release release(int)} to defer releasing a
- * {@link #WAKE_BIT_PROXIMITY_SCREEN_OFF} wakelock until the proximity sensor returns
- * a negative value.
+ * Mask for the wake lock level component of a combined wake lock level and flags integer.
*
- * {@hide}
+ * @hide
*/
- public static final int WAIT_FOR_PROXIMITY_NEGATIVE = 1;
+ public static final int WAKE_LOCK_LEVEL_MASK = 0x0000ffff;
/**
+ * Wake lock flag: Turn the screen on when the wake lock is acquired.
+ * <p>
* Normally wake locks don't actually wake the device, they just cause
- * it to remain on once it's already on. Think of the video player
- * app as the normal behavior. Notifications that pop up and want
+ * the screen to remain on once it's already on. Think of the video player
+ * application as the normal behavior. Notifications that pop up and want
* the device to be on are the exception; use this flag to be like them.
- * <p>
- * Does not work with PARTIAL_WAKE_LOCKs.
+ * </p><p>
+ * Cannot be used with {@link #PARTIAL_WAKE_LOCK}.
+ * </p>
*/
public static final int ACQUIRE_CAUSES_WAKEUP = 0x10000000;
/**
- * When this wake lock is released, poke the user activity timer
+ * Wake lock flag: When this wake lock is released, poke the user activity timer
* so the screen stays on for a little longer.
* <p>
- * Will not turn the screen on if it is not already on. See {@link #ACQUIRE_CAUSES_WAKEUP}
- * if you want that.
- * <p>
- * Does not work with PARTIAL_WAKE_LOCKs.
+ * Will not turn the screen on if it is not already on.
+ * See {@link #ACQUIRE_CAUSES_WAKEUP} if you want that.
+ * </p><p>
+ * Cannot be used with {@link #PARTIAL_WAKE_LOCK}.
+ * </p>
*/
public static final int ON_AFTER_RELEASE = 0x20000000;
/**
- * Brightness value to use when battery is low.
- * @hide
+ * Flag for {@link WakeLock#release release(int)} to defer releasing a
+ * {@link #WAKE_BIT_PROXIMITY_SCREEN_OFF} wake lock until the proximity sensor returns
+ * a negative value.
+ *
+ * {@hide}
*/
- public static final int BRIGHTNESS_LOW_BATTERY = 10;
+ public static final int WAIT_FOR_PROXIMITY_NEGATIVE = 1;
/**
* Brightness value for fully on.
@@ -211,46 +234,181 @@ public class PowerManager
public static final int BRIGHTNESS_ON = 255;
/**
- * Brightness value for dim backlight.
+ * Brightness value for fully off.
+ * @hide
+ */
+ public static final int BRIGHTNESS_OFF = 0;
+
+ // Note: Be sure to update android.os.BatteryStats and PowerManager.h
+ // if adding or modifying user activity event constants.
+
+ /**
+ * User activity event type: Unspecified event type.
* @hide
*/
- public static final int BRIGHTNESS_DIM = 20;
+ public static final int USER_ACTIVITY_EVENT_OTHER = 0;
/**
- * Brightness value for fully off.
+ * User activity event type: Button or key pressed or released.
* @hide
*/
- public static final int BRIGHTNESS_OFF = 0;
+ public static final int USER_ACTIVITY_EVENT_BUTTON = 1;
/**
- * Class lets you say that you need to have the device on.
- * <p>
- * Call release when you are done and don't need the lock anymore.
+ * User activity event type: Touch down, move or up.
+ * @hide
+ */
+ public static final int USER_ACTIVITY_EVENT_TOUCH = 2;
+
+ /**
+ * User activity flag: Do not restart the user activity timeout or brighten
+ * the display in response to user activity if it is already dimmed.
+ * @hide
+ */
+ public static final int USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS = 1 << 0;
+
+ /**
+ * Go to sleep reason code: Going to sleep due by user request.
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_USER = 0;
+
+ /**
+ * Go to sleep reason code: Going to sleep due by request of the
+ * device administration policy.
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_DEVICE_ADMIN = 1;
+
+ /**
+ * Go to sleep reason code: Going to sleep due to a screen timeout.
+ * @hide
+ */
+ public static final int GO_TO_SLEEP_REASON_TIMEOUT = 2;
+
+ final Context mContext;
+ final IPowerManager mService;
+ final Handler mHandler;
+
+ /**
+ * {@hide}
+ */
+ public PowerManager(Context context, IPowerManager service, Handler handler) {
+ mContext = context;
+ mService = service;
+ mHandler = handler;
+ }
+
+ /**
+ * Gets the minimum supported screen brightness setting.
+ * The screen may be allowed to become dimmer than this value but
+ * this is the minimum value that can be set by the user.
+ * @hide
+ */
+ public int getMinimumScreenBrightnessSetting() {
+ return mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_screenBrightnessSettingMinimum);
+ }
+
+ /**
+ * Gets the maximum supported screen brightness setting.
+ * The screen may be allowed to become dimmer than this value but
+ * this is the maximum value that can be set by the user.
+ * @hide
+ */
+ public int getMaximumScreenBrightnessSetting() {
+ return mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_screenBrightnessSettingMaximum);
+ }
+
+ /**
+ * Gets the default screen brightness setting.
+ * @hide
+ */
+ public int getDefaultScreenBrightnessSetting() {
+ return mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_screenBrightnessSettingDefault);
+ }
+
+ /**
+ * Returns true if the screen auto-brightness adjustment setting should
+ * be available in the UI. This setting is experimental and disabled by default.
+ * @hide
+ */
+ public static boolean useScreenAutoBrightnessAdjustmentFeature() {
+ return SystemProperties.getBoolean("persist.power.useautobrightadj", false);
+ }
+
+ /**
+ * Returns true if the twilight service should be used to adjust screen brightness
+ * policy. This setting is experimental and disabled by default.
+ * @hide
+ */
+ public static boolean useTwilightAdjustmentFeature() {
+ return SystemProperties.getBoolean("persist.power.usetwilightadj", false);
+ }
+
+ /**
+ * Creates a new wake lock with the specified level and flags.
* <p>
- * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
- * permission in an {@code &lt;uses-permission&gt;} element of the application's manifest.
+ * The {@code levelAndFlags} parameter specifies a wake lock level and optional flags
+ * combined using the logical OR operator.
+ * </p><p>
+ * The wake lock levels are: {@link #PARTIAL_WAKE_LOCK},
+ * {@link #FULL_WAKE_LOCK}, {@link #SCREEN_DIM_WAKE_LOCK}
+ * and {@link #SCREEN_BRIGHT_WAKE_LOCK}. Exactly one wake lock level must be
+ * specified as part of the {@code levelAndFlags} parameter.
+ * </p><p>
+ * The wake lock flags are: {@link #ACQUIRE_CAUSES_WAKEUP}
+ * and {@link #ON_AFTER_RELEASE}. Multiple flags can be combined as part of the
+ * {@code levelAndFlags} parameters.
+ * </p><p>
+ * Call {@link WakeLock#acquire() acquire()} on the object to acquire the
+ * wake lock, and {@link WakeLock#release release()} when you are done.
+ * </p><p>
+ * {@samplecode
+ * PowerManager pm = (PowerManager)mContext.getSystemService(
+ * Context.POWER_SERVICE);
+ * PowerManager.WakeLock wl = pm.newWakeLock(
+ * PowerManager.SCREEN_DIM_WAKE_LOCK
+ * | PowerManager.ON_AFTER_RELEASE,
+ * TAG);
+ * wl.acquire();
+ * // ... do work...
+ * wl.release();
+ * }
+ * </p><p>
+ * Although a wake lock can be created without special permissions,
+ * the {@link android.Manifest.permission#WAKE_LOCK} permission is
+ * required to actually acquire or release the wake lock that is returned.
+ * </p><p class="note">
+ * If using this to keep the screen on, you should strongly consider using
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
+ * This window flag will be correctly managed by the platform
+ * as the user moves between applications and doesn't require a special permission.
+ * </p>
+ *
+ * @param levelAndFlags Combination of wake lock level and flag values defining
+ * the requested behavior of the WakeLock.
+ * @param tag Your class name (or other tag) for debugging purposes.
+ *
+ * @see WakeLock#acquire()
+ * @see WakeLock#release()
+ * @see #PARTIAL_WAKE_LOCK
+ * @see #FULL_WAKE_LOCK
+ * @see #SCREEN_DIM_WAKE_LOCK
+ * @see #SCREEN_BRIGHT_WAKE_LOCK
+ * @see #ACQUIRE_CAUSES_WAKEUP
+ * @see #ON_AFTER_RELEASE
*/
- public class WakeLock
- {
- static final int RELEASE_WAKE_LOCK = 1;
+ public WakeLock newWakeLock(int levelAndFlags, String tag) {
+ validateWakeLockParameters(levelAndFlags, tag);
+ return new WakeLock(levelAndFlags, tag);
+ }
- Runnable mReleaser = new Runnable() {
- public void run() {
- release();
- }
- };
-
- int mFlags;
- String mTag;
- IBinder mToken;
- int mCount = 0;
- boolean mRefCounted = true;
- boolean mHeld = false;
- WorkSource mWorkSource;
-
- WakeLock(int flags, String tag)
- {
- switch (flags & LOCK_MASK) {
+ /** @hide */
+ public static void validateWakeLockParameters(int levelAndFlags, String tag) {
+ switch (levelAndFlags & WAKE_LOCK_LEVEL_MASK) {
case PARTIAL_WAKE_LOCK:
case SCREEN_DIM_WAKE_LOCK:
case SCREEN_BRIGHT_WAKE_LOCK:
@@ -258,42 +416,282 @@ public class PowerManager
case PROXIMITY_SCREEN_OFF_WAKE_LOCK:
break;
default:
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Must specify a valid wake lock level.");
+ }
+ if (tag == null) {
+ throw new IllegalArgumentException("The tag must not be null.");
+ }
+ }
+
+ /**
+ * Notifies the power manager that user activity happened.
+ * <p>
+ * Resets the auto-off timer and brightens the screen if the device
+ * is not asleep. This is what happens normally when a key or the touch
+ * screen is pressed or when some other user activity occurs.
+ * This method does not wake up the device if it has been put to sleep.
+ * </p><p>
+ * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+ * </p>
+ *
+ * @param when The time of the user activity, in the {@link SystemClock#uptimeMillis()}
+ * time base. This timestamp is used to correctly order the user activity request with
+ * other power management functions. It should be set
+ * to the timestamp of the input event that caused the user activity.
+ * @param noChangeLights If true, does not cause the keyboard backlight to turn on
+ * because of this event. This is set when the power key is pressed.
+ * We want the device to stay on while the button is down, but we're about
+ * to turn off the screen so we don't want the keyboard backlight to turn on again.
+ * Otherwise the lights flash on and then off and it looks weird.
+ *
+ * @see #wakeUp
+ * @see #goToSleep
+ */
+ public void userActivity(long when, boolean noChangeLights) {
+ try {
+ mService.userActivity(when, USER_ACTIVITY_EVENT_OTHER,
+ noChangeLights ? USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS : 0);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Forces the device to go to sleep.
+ * <p>
+ * Overrides all the wake locks that are held.
+ * This is what happens when the power key is pressed to turn off the screen.
+ * </p><p>
+ * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+ * </p>
+ *
+ * @param time The time when the request to go to sleep was issued, in the
+ * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly
+ * order the go to sleep request with other power management functions. It should be set
+ * to the timestamp of the input event that caused the request to go to sleep.
+ *
+ * @see #userActivity
+ * @see #wakeUp
+ */
+ public void goToSleep(long time) {
+ try {
+ mService.goToSleep(time, GO_TO_SLEEP_REASON_USER);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Forces the device to wake up from sleep.
+ * <p>
+ * If the device is currently asleep, wakes it up, otherwise does nothing.
+ * This is what happens when the power key is pressed to turn on the screen.
+ * </p><p>
+ * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+ * </p>
+ *
+ * @param time The time when the request to wake up was issued, in the
+ * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly
+ * order the wake up request with other power management functions. It should be set
+ * to the timestamp of the input event that caused the request to wake up.
+ *
+ * @see #userActivity
+ * @see #goToSleep
+ */
+ public void wakeUp(long time) {
+ try {
+ mService.wakeUp(time);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Forces the device to start napping.
+ * <p>
+ * If the device is currently awake, starts dreaming, otherwise does nothing.
+ * When the dream ends or if the dream cannot be started, the device will
+ * either wake up or go to sleep depending on whether there has been recent
+ * user activity.
+ * </p><p>
+ * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+ * </p>
+ *
+ * @param time The time when the request to nap was issued, in the
+ * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly
+ * order the nap request with other power management functions. It should be set
+ * to the timestamp of the input event that caused the request to nap.
+ *
+ * @see #wakeUp
+ * @see #goToSleep
+ *
+ * @hide
+ */
+ public void nap(long time) {
+ try {
+ mService.nap(time);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Sets the brightness of the backlights (screen, keyboard, button).
+ * <p>
+ * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+ * </p>
+ *
+ * @param brightness The brightness value from 0 to 255.
+ *
+ * {@hide}
+ */
+ public void setBacklightBrightness(int brightness) {
+ try {
+ mService.setTemporaryScreenBrightnessSettingOverride(brightness);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Returns true if the specified wake lock level is supported.
+ *
+ * @param level The wake lock level to check.
+ * @return True if the specified wake lock level is supported.
+ *
+ * {@hide}
+ */
+ public boolean isWakeLockLevelSupported(int level) {
+ try {
+ return mService.isWakeLockLevelSupported(level);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether the screen is currently on.
+ * <p>
+ * Only indicates whether the screen is on. The screen could be either bright or dim.
+ * </p><p>
+ * {@samplecode
+ * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ * boolean isScreenOn = pm.isScreenOn();
+ * }
+ * </p>
+ *
+ * @return whether the screen is on (bright or dim).
+ */
+ public boolean isScreenOn() {
+ try {
+ return mService.isScreenOn();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Reboot the device. Will not return if the reboot is successful.
+ * <p>
+ * Requires the {@link android.Manifest.permission#REBOOT} permission.
+ * </p>
+ *
+ * @param reason code to pass to the kernel (e.g., "recovery") to
+ * request special boot modes, or null.
+ */
+ public void reboot(String reason) {
+ try {
+ mService.reboot(reason);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * A wake lock is a mechanism to indicate that your application needs
+ * to have the device stay on.
+ * <p>
+ * Any application using a WakeLock must request the {@code android.permission.WAKE_LOCK}
+ * permission in an {@code &lt;uses-permission&gt;} element of the application's manifest.
+ * Obtain a wake lock by calling {@link PowerManager#newWakeLock(int, String)}.
+ * </p><p>
+ * Call {@link #acquire()} to acquire the wake lock and force the device to stay
+ * on at the level that was requested when the wake lock was created.
+ * </p><p>
+ * Call {@link #release()} when you are done and don't need the lock anymore.
+ * It is very important to do this as soon as possible to avoid running down the
+ * device's battery excessively.
+ * </p>
+ */
+ public final class WakeLock {
+ private final int mFlags;
+ private final String mTag;
+ private final IBinder mToken;
+ private int mCount;
+ private boolean mRefCounted = true;
+ private boolean mHeld;
+ private WorkSource mWorkSource;
+
+ private final Runnable mReleaser = new Runnable() {
+ public void run() {
+ release();
}
+ };
+ WakeLock(int flags, String tag) {
mFlags = flags;
mTag = tag;
mToken = new Binder();
}
+ @Override
+ protected void finalize() throws Throwable {
+ synchronized (mToken) {
+ if (mHeld) {
+ Log.wtf(TAG, "WakeLock finalized while still held: " + mTag);
+ try {
+ mService.releaseWakeLock(mToken, 0);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
/**
- * Sets whether this WakeLock is ref counted.
- *
- * <p>Wake locks are reference counted by default.
+ * Sets whether this WakeLock is reference counted.
+ * <p>
+ * Wake locks are reference counted by default. If a wake lock is
+ * reference counted, then each call to {@link #acquire()} must be
+ * balanced by an equal number of calls to {@link #release()}. If a wake
+ * lock is not reference counted, then one call to {@link #release()} is
+ * sufficient to undo the effect of all previous calls to {@link #acquire()}.
+ * </p>
*
- * @param value true for ref counted, false for not ref counted.
+ * @param value True to make the wake lock reference counted, false to
+ * make the wake lock non-reference counted.
*/
- public void setReferenceCounted(boolean value)
- {
- mRefCounted = value;
+ public void setReferenceCounted(boolean value) {
+ synchronized (mToken) {
+ mRefCounted = value;
+ }
}
/**
- * Makes sure the device is on at the level you asked when you created
- * the wake lock.
+ * Acquires the wake lock.
+ * <p>
+ * Ensures that the device is on at the level requested when
+ * the wake lock was created.
+ * </p>
*/
- public void acquire()
- {
+ public void acquire() {
synchronized (mToken) {
acquireLocked();
}
}
/**
- * Makes sure the device is on at the level you asked when you created
- * the wake lock. The lock will be released after the given timeout.
- *
- * @param timeout Release the lock after the give timeout in milliseconds.
+ * Acquires the wake lock with a timeout.
+ * <p>
+ * Ensures that the device is on at the level requested when
+ * the wake lock was created. The lock will be released after the given timeout
+ * expires.
+ * </p>
+ *
+ * @param timeout The timeout after which to release the wake lock, in milliseconds.
*/
public void acquire(long timeout) {
synchronized (mToken) {
@@ -301,12 +699,18 @@ public class PowerManager
mHandler.postDelayed(mReleaser, timeout);
}
}
-
+
private void acquireLocked() {
if (!mRefCounted || mCount++ == 0) {
+ // Do this even if the wake lock is already thought to be held (mHeld == true)
+ // because non-reference counted wake locks are not always properly released.
+ // For example, the keyguard's wake lock might be forcibly released by the
+ // power manager without the keyguard knowing. A subsequent call to acquire
+ // should immediately acquire the wake lock once again despite never having
+ // been explicitly released by the keyguard.
mHandler.removeCallbacks(mReleaser);
try {
- mService.acquireWakeLock(mFlags, mToken, mTag, mWorkSource);
+ mService.acquireWakeLock(mToken, mFlags, mTag, mWorkSource);
} catch (RemoteException e) {
}
mHeld = true;
@@ -314,24 +718,27 @@ public class PowerManager
}
/**
- * Release your claim to the CPU or screen being on.
- *
+ * Releases the wake lock.
* <p>
- * It may turn off shortly after you release it, or it may not if there
- * are other wake locks held.
+ * This method releases your claim to the CPU or screen being on.
+ * The screen may turn off shortly after you release the wake lock, or it may
+ * not if there are other wake locks still held.
+ * </p>
*/
public void release() {
release(0);
}
/**
- * Release your claim to the CPU or screen being on.
- * @param flags Combination of flag values to modify the release behavior.
- * Currently only {@link #WAIT_FOR_PROXIMITY_NEGATIVE} is supported.
- *
+ * Releases the wake lock with flags to modify the release behavior.
* <p>
- * It may turn off shortly after you release it, or it may not if there
- * are other wake locks held.
+ * This method releases your claim to the CPU or screen being on.
+ * The screen may turn off shortly after you release the wake lock, or it may
+ * not if there are other wake locks still held.
+ * </p>
+ *
+ * @param flags Combination of flag values to modify the release behavior.
+ * Currently only {@link #WAIT_FOR_PROXIMITY_NEGATIVE} is supported.
*
* {@hide}
*/
@@ -339,11 +746,13 @@ public class PowerManager
synchronized (mToken) {
if (!mRefCounted || --mCount == 0) {
mHandler.removeCallbacks(mReleaser);
- try {
- mService.releaseWakeLock(mToken, flags);
- } catch (RemoteException e) {
+ if (mHeld) {
+ try {
+ mService.releaseWakeLock(mToken, flags);
+ } catch (RemoteException e) {
+ }
+ mHeld = false;
}
- mHeld = false;
}
if (mCount < 0) {
throw new RuntimeException("WakeLock under-locked " + mTag);
@@ -351,23 +760,40 @@ public class PowerManager
}
}
- public boolean isHeld()
- {
+ /**
+ * Returns true if the wake lock has been acquired but not yet released.
+ *
+ * @return True if the wake lock is held.
+ */
+ public boolean isHeld() {
synchronized (mToken) {
return mHeld;
}
}
+ /**
+ * Sets the work source associated with the wake lock.
+ * <p>
+ * The work source is used to determine on behalf of which application
+ * the wake lock is being held. This is useful in the case where a
+ * service is performing work on behalf of an application so that the
+ * cost of that work can be accounted to the application.
+ * </p>
+ *
+ * @param ws The work source, or null if none.
+ */
public void setWorkSource(WorkSource ws) {
synchronized (mToken) {
if (ws != null && ws.size() == 0) {
ws = null;
}
- boolean changed = true;
+
+ final boolean changed;
if (ws == null) {
+ changed = mWorkSource != null;
mWorkSource = null;
} else if (mWorkSource == null) {
- changed = mWorkSource != null;
+ changed = true;
mWorkSource = new WorkSource(ws);
} else {
changed = mWorkSource.diff(ws);
@@ -375,6 +801,7 @@ public class PowerManager
mWorkSource.set(ws);
}
}
+
if (changed && mHeld) {
try {
mService.updateWakeLockWorkSource(mToken, mWorkSource);
@@ -384,6 +811,7 @@ public class PowerManager
}
}
+ @Override
public String toString() {
synchronized (mToken) {
return "WakeLock{"
@@ -391,194 +819,5 @@ public class PowerManager
+ " held=" + mHeld + ", refCount=" + mCount + "}";
}
}
-
- @Override
- protected void finalize() throws Throwable
- {
- synchronized (mToken) {
- if (mHeld) {
- Log.wtf(TAG, "WakeLock finalized while still held: " + mTag);
- try {
- mService.releaseWakeLock(mToken, 0);
- } catch (RemoteException e) {
- }
- }
- }
- }
}
-
- /**
- * Get a wake lock at the level of the flags parameter. Call
- * {@link WakeLock#acquire() acquire()} on the object to acquire the
- * wake lock, and {@link WakeLock#release release()} when you are done.
- *
- * {@samplecode
- *PowerManager pm = (PowerManager)mContext.getSystemService(
- * Context.POWER_SERVICE);
- *PowerManager.WakeLock wl = pm.newWakeLock(
- * PowerManager.SCREEN_DIM_WAKE_LOCK
- * | PowerManager.ON_AFTER_RELEASE,
- * TAG);
- *wl.acquire();
- * // ...
- *wl.release();
- * }
- *
- * <p class="note">If using this to keep the screen on, you should strongly consider using
- * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
- * This window flag will be correctly managed by the platform
- * as the user moves between applications and doesn't require a special permission.</p>
- *
- * @param flags Combination of flag values defining the requested behavior of the WakeLock.
- * @param tag Your class name (or other tag) for debugging purposes.
- *
- * @see WakeLock#acquire()
- * @see WakeLock#release()
- */
- public WakeLock newWakeLock(int flags, String tag)
- {
- if (tag == null) {
- throw new NullPointerException("tag is null in PowerManager.newWakeLock");
- }
- return new WakeLock(flags, tag);
- }
-
- /**
- * User activity happened.
- * <p>
- * Turns the device from whatever state it's in to full on, and resets
- * the auto-off timer.
- *
- * @param when is used to order this correctly with the wake lock calls.
- * This time should be in the {@link SystemClock#uptimeMillis
- * SystemClock.uptimeMillis()} time base.
- * @param noChangeLights should be true if you don't want the lights to
- * turn on because of this event. This is set when the power
- * key goes down. We want the device to stay on while the button
- * is down, but we're about to turn off. Otherwise the lights
- * flash on and then off and it looks weird.
- */
- public void userActivity(long when, boolean noChangeLights)
- {
- try {
- mService.userActivity(when, noChangeLights);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * Force the device to go to sleep. Overrides all the wake locks that are
- * held.
- *
- * @param time is used to order this correctly with the wake lock calls.
- * The time should be in the {@link SystemClock#uptimeMillis
- * SystemClock.uptimeMillis()} time base.
- */
- public void goToSleep(long time)
- {
- try {
- mService.goToSleep(time);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * sets the brightness of the backlights (screen, keyboard, button).
- *
- * @param brightness value from 0 to 255
- *
- * {@hide}
- */
- public void setBacklightBrightness(int brightness)
- {
- try {
- mService.setBacklightBrightness(brightness);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * Returns the set of flags for {@link #newWakeLock(int, String) newWakeLock()}
- * that are supported on the device.
- * For example, to test to see if the {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK}
- * is supported:
- *
- * {@samplecode
- * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- * int supportedFlags = pm.getSupportedWakeLockFlags();
- * boolean proximitySupported = ((supportedFlags & PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)
- * == PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK);
- * }
- *
- * @return the set of supported WakeLock flags.
- *
- * {@hide}
- */
- public int getSupportedWakeLockFlags()
- {
- try {
- return mService.getSupportedWakeLockFlags();
- } catch (RemoteException e) {
- return 0;
- }
- }
-
- /**
- * Returns whether the screen is currently on. The screen could be bright
- * or dim.
- *
- * {@samplecode
- * PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- * boolean isScreenOn = pm.isScreenOn();
- * }
- *
- * @return whether the screen is on (bright or dim).
- */
- public boolean isScreenOn()
- {
- try {
- return mService.isScreenOn();
- } catch (RemoteException e) {
- return false;
- }
- }
-
- /**
- * Reboot the device. Will not return if the reboot is
- * successful. Requires the {@link android.Manifest.permission#REBOOT}
- * permission.
- *
- * @param reason code to pass to the kernel (e.g., "recovery") to
- * request special boot modes, or null.
- */
- public void reboot(String reason)
- {
- try {
- mService.reboot(reason);
- } catch (RemoteException e) {
- }
- }
-
- private PowerManager()
- {
- }
-
- /**
- * {@hide}
- */
- public PowerManager(IPowerManager service, Handler handler)
- {
- mService = service;
- mHandler = handler;
- }
-
- /**
- * TODO: It would be nice to be able to set the poke lock here,
- * but I'm not sure what would be acceptable as an interface -
- * either a PokeLock object (like WakeLock) or, possibly just a
- * method call to set the poke lock.
- */
-
- IPowerManager mService;
- Handler mHandler;
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 6ab4dc1..05099fb 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -116,6 +116,12 @@ public class Process {
public static final int NFC_UID = 1027;
/**
+ * Defines the UID/GID for the Bluetooth service process.
+ * @hide
+ */
+ public static final int BLUETOOTH_UID = 1002;
+
+ /**
* Defines the GID for the group that allows write access to the internal media storage.
* @hide
*/
@@ -146,10 +152,24 @@ public class Process {
public static final int LAST_ISOLATED_UID = 99999;
/**
+ * First gid for applications to share resources. Used when forward-locking
+ * is enabled but all UserHandles need to be able to read the resources.
+ * @hide
+ */
+ public static final int FIRST_SHARED_APPLICATION_GID = 50000;
+
+ /**
+ * Last gid for applications to share resources. Used when forward-locking
+ * is enabled but all UserHandles need to be able to read the resources.
+ * @hide
+ */
+ public static final int LAST_SHARED_APPLICATION_GID = 59999;
+
+ /**
* Defines a secondary group id for access to the bluetooth hardware.
*/
public static final int BLUETOOTH_GID = 2000;
-
+
/**
* Standard priority of application threads.
* Use with {@link #setThreadPriority(int)} and
@@ -370,12 +390,13 @@ public class Process {
public static final ProcessStartResult start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
- int debugFlags, int targetSdkVersion,
+ int debugFlags, int mountExternal,
+ int targetSdkVersion,
String seInfo,
String[] zygoteArgs) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
- debugFlags, targetSdkVersion, seInfo, zygoteArgs);
+ debugFlags, mountExternal, targetSdkVersion, seInfo, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -547,7 +568,8 @@ public class Process {
final String niceName,
final int uid, final int gid,
final int[] gids,
- int debugFlags, int targetSdkVersion,
+ int debugFlags, int mountExternal,
+ int targetSdkVersion,
String seInfo,
String[] extraArgs)
throws ZygoteStartFailedEx {
@@ -574,6 +596,11 @@ public class Process {
if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
argsForZygote.add("--enable-assert");
}
+ if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER) {
+ argsForZygote.add("--mount-external-multiuser");
+ } else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL) {
+ argsForZygote.add("--mount-external-multiuser-all");
+ }
argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
//TODO optionally enable debuger
@@ -634,16 +661,29 @@ public class Process {
public static final native int myTid();
/**
- * Returns the identifier of this process's user.
+ * Returns the identifier of this process's uid. This is the kernel uid
+ * that the process is running under, which is the identity of its
+ * app-specific sandbox. It is different from {@link #myUserHandle} in that
+ * a uid identifies a specific app sandbox in a specific user.
*/
public static final native int myUid();
/**
+ * Returns this process's user handle. This is the
+ * user the process is running under. It is distinct from
+ * {@link #myUid()} in that a particular user will have multiple
+ * distinct apps running under it each with their own uid.
+ */
+ public static final UserHandle myUserHandle() {
+ return new UserHandle(UserHandle.getUserId(myUid()));
+ }
+
+ /**
* Returns whether the current process is in an isolated sandbox.
* @hide
*/
public static final boolean isIsolated() {
- int uid = UserId.getAppId(myUid());
+ int uid = UserHandle.getAppId(myUid());
return uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID;
}
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 43cf74e..480fe7d 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -40,6 +40,7 @@ import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -326,7 +327,8 @@ public class RecoverySystem {
throws IOException {
String filename = packageFile.getCanonicalPath();
Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
- String arg = "--update_package=" + filename;
+ String arg = "--update_package=" + filename +
+ "\n--locale=" + Locale.getDefault().toString();
bootCommand(context, arg);
}
@@ -346,7 +348,8 @@ public class RecoverySystem {
final ConditionVariable condition = new ConditionVariable();
Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
- context.sendOrderedBroadcast(intent, android.Manifest.permission.MASTER_CLEAR,
+ context.sendOrderedBroadcastAsUser(intent, UserHandle.OWNER,
+ android.Manifest.permission.MASTER_CLEAR,
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -357,7 +360,7 @@ public class RecoverySystem {
// Block until the ordered broadcast has completed.
condition.block();
- bootCommand(context, "--wipe_data");
+ bootCommand(context, "--wipe_data\n--locale=" + Locale.getDefault().toString());
}
/**
@@ -365,7 +368,7 @@ public class RecoverySystem {
* @throws IOException if something goes wrong.
*/
public static void rebootWipeCache(Context context) throws IOException {
- bootCommand(context, "--wipe_cache");
+ bootCommand(context, "--wipe_cache\n--locale=" + Locale.getDefault().toString());
}
/**
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index b74af16..d02a320 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -304,4 +304,25 @@ public class RemoteCallbackList<E extends IInterface> {
mBroadcastCount = -1;
}
+
+ /**
+ * Returns the number of registered callbacks. Note that the number of registered
+ * callbacks may differ from the value returned by {@link #beginBroadcast()} since
+ * the former returns the number of callbacks registered at the time of the call
+ * and the second the number of callback to which the broadcast will be delivered.
+ * <p>
+ * This function is useful to decide whether to schedule a broadcast if this
+ * requires doing some work which otherwise would not be performed.
+ * </p>
+ *
+ * @return The size.
+ */
+ public int getRegisteredCallbackCount() {
+ synchronized (mCallbacks) {
+ if (mKilled) {
+ return 0;
+ }
+ return mCallbacks.size();
+ }
+ }
}
diff --git a/core/java/android/os/SchedulingPolicyService.java b/core/java/android/os/SchedulingPolicyService.java
index 94f907b..a3fede6 100644
--- a/core/java/android/os/SchedulingPolicyService.java
+++ b/core/java/android/os/SchedulingPolicyService.java
@@ -33,7 +33,7 @@ public class SchedulingPolicyService extends ISchedulingPolicyService.Stub {
// Minimum and maximum values allowed for requestPriority parameter prio
private static final int PRIORITY_MIN = 1;
- private static final int PRIORITY_MAX = 2;
+ private static final int PRIORITY_MAX = 3;
public SchedulingPolicyService() {
}
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index 7291739..c9adf45 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -46,15 +46,16 @@ package android.os;
* such as {@link Thread#sleep(long) Thread.sleep(millls)},
* {@link Object#wait(long) Object.wait(millis)}, and
* {@link System#nanoTime System.nanoTime()}. This clock is guaranteed
- * to be monotonic, and is the recommended basis for the general purpose
- * interval timing of user interface events, performance measurements,
- * and anything else that does not need to measure elapsed time during
- * device sleep. Most methods that accept a timestamp value expect the
- * {@link #uptimeMillis} clock.
+ * to be monotonic, and is suitable for interval timing when the
+ * interval does not span device sleep. Most methods that accept a
+ * timestamp value currently expect the {@link #uptimeMillis} clock.
+ *
+ * <li> <p> {@link #elapsedRealtime} and {@link #elapsedRealtimeNanos}
+ * return the time since the system was booted, and include deep sleep.
+ * This clock is guaranteed to be monotonic, and continues to tick even
+ * when the CPU is in power saving modes, so is the recommend basis
+ * for general purpose interval timing.
*
- * <li> <p> {@link #elapsedRealtime} is counted in milliseconds since the
- * system was booted, including deep sleep. This clock should be used
- * when measuring time intervals that may span periods of system sleep.
* </ul>
*
* There are several mechanisms for controlling the timing of events:
@@ -150,7 +151,14 @@ public final class SystemClock {
* @return elapsed milliseconds since boot.
*/
native public static long elapsedRealtime();
-
+
+ /**
+ * Returns nanoseconds since boot, including time spent in sleep.
+ *
+ * @return elapsed nanoseconds since boot.
+ */
+ public static native long elapsedRealtimeNanos();
+
/**
* Returns milliseconds running in the current thread.
*
diff --git a/core/java/android/os/SystemService.java b/core/java/android/os/SystemService.java
index da27db5..f345271 100644
--- a/core/java/android/os/SystemService.java
+++ b/core/java/android/os/SystemService.java
@@ -16,15 +16,55 @@
package android.os;
-/** @hide */
-public class SystemService
-{
- /** Request that the init daemon start a named service. */
+import android.util.Slog;
+
+import com.google.android.collect.Maps;
+
+import java.util.HashMap;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Controls and utilities for low-level {@code init} services.
+ *
+ * @hide
+ */
+public class SystemService {
+
+ private static HashMap<String, State> sStates = Maps.newHashMap();
+
+ /**
+ * State of a known {@code init} service.
+ */
+ public enum State {
+ RUNNING("running"),
+ STOPPING("stopping"),
+ STOPPED("stopped"),
+ RESTARTING("restarting");
+
+ State(String state) {
+ sStates.put(state, this);
+ }
+ }
+
+ private static Object sPropertyLock = new Object();
+
+ static {
+ SystemProperties.addChangeCallback(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (sPropertyLock) {
+ sPropertyLock.notifyAll();
+ }
+ }
+ });
+ }
+
+ /** Request that the init daemon start a named service. */
public static void start(String name) {
SystemProperties.set("ctl.start", name);
}
-
- /** Request that the init daemon stop a named service. */
+
+ /** Request that the init daemon stop a named service. */
public static void stop(String name) {
SystemProperties.set("ctl.stop", name);
}
@@ -33,4 +73,77 @@ public class SystemService
public static void restart(String name) {
SystemProperties.set("ctl.restart", name);
}
+
+ /**
+ * Return current state of given service.
+ */
+ public static State getState(String service) {
+ final String rawState = SystemProperties.get("init.svc." + service);
+ final State state = sStates.get(rawState);
+ if (state != null) {
+ return state;
+ } else {
+ return State.STOPPED;
+ }
+ }
+
+ /**
+ * Check if given service is {@link State#STOPPED}.
+ */
+ public static boolean isStopped(String service) {
+ return State.STOPPED.equals(getState(service));
+ }
+
+ /**
+ * Check if given service is {@link State#RUNNING}.
+ */
+ public static boolean isRunning(String service) {
+ return State.RUNNING.equals(getState(service));
+ }
+
+ /**
+ * Wait until given service has entered specific state.
+ */
+ public static void waitForState(String service, State state, long timeoutMillis)
+ throws TimeoutException {
+ final long endMillis = SystemClock.elapsedRealtime() + timeoutMillis;
+ while (true) {
+ synchronized (sPropertyLock) {
+ final State currentState = getState(service);
+ if (state.equals(currentState)) {
+ return;
+ }
+
+ if (SystemClock.elapsedRealtime() >= endMillis) {
+ throw new TimeoutException("Service " + service + " currently " + currentState
+ + "; waited " + timeoutMillis + "ms for " + state);
+ }
+
+ try {
+ sPropertyLock.wait(timeoutMillis);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+
+ /**
+ * Wait until any of given services enters {@link State#STOPPED}.
+ */
+ public static void waitForAnyStopped(String... services) {
+ while (true) {
+ synchronized (sPropertyLock) {
+ for (String service : services) {
+ if (State.STOPPED.equals(getState(service))) {
+ return;
+ }
+ }
+
+ try {
+ sPropertyLock.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index d2050b7..59d0f7a 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -39,11 +39,12 @@ public final class Trace {
public static final long TRACE_TAG_SYNC_MANAGER = 1L << 7;
public static final long TRACE_TAG_AUDIO = 1L << 8;
public static final long TRACE_TAG_VIDEO = 1L << 9;
+ public static final long TRACE_TAG_CAMERA = 1L << 10;
public static final int TRACE_FLAGS_START_BIT = 1;
public static final String[] TRACE_TAGS = {
"Graphics", "Input", "View", "WebView", "Window Manager",
- "Activity Manager", "Sync Manager", "Audio", "Video",
+ "Activity Manager", "Sync Manager", "Audio", "Video", "Camera",
};
public static final String PROPERTY_TRACE_TAG_ENABLEFLAGS = "debug.atrace.tags.enableflags";
diff --git a/core/java/android/os/UEventObserver.java b/core/java/android/os/UEventObserver.java
index b924e84..d33382b 100644
--- a/core/java/android/os/UEventObserver.java
+++ b/core/java/android/os/UEventObserver.java
@@ -37,14 +37,79 @@ import java.util.HashMap;
* @hide
*/
public abstract class UEventObserver {
- private static final String TAG = UEventObserver.class.getSimpleName();
+ private static UEventThread sThread;
+
+ private static native void native_setup();
+ private static native int next_event(byte[] buffer);
+
+ public UEventObserver() {
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ stopObserving();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private static UEventThread getThread() {
+ synchronized (UEventObserver.class) {
+ if (sThread == null) {
+ sThread = new UEventThread();
+ sThread.start();
+ }
+ return sThread;
+ }
+ }
+
+ private static UEventThread peekThread() {
+ synchronized (UEventObserver.class) {
+ return sThread;
+ }
+ }
+
+ /**
+ * Begin observation of UEvent's.<p>
+ * This method will cause the UEvent thread to start if this is the first
+ * invocation of startObserving in this process.<p>
+ * Once called, the UEvent thread will call onUEvent() when an incoming
+ * UEvent matches the specified string.<p>
+ * This method can be called multiple times to register multiple matches.
+ * Only one call to stopObserving is required even with multiple registered
+ * matches.
+ * @param match A substring of the UEvent to match. Use "" to match all
+ * UEvent's
+ */
+ public final void startObserving(String match) {
+ final UEventThread t = getThread();
+ t.addObserver(match, this);
+ }
+
+ /**
+ * End observation of UEvent's.<p>
+ * This process's UEvent thread will never call onUEvent() on this
+ * UEventObserver after this call. Repeated calls have no effect.
+ */
+ public final void stopObserving() {
+ final UEventThread t = getThread();
+ if (t != null) {
+ t.removeObserver(this);
+ }
+ }
+
+ /**
+ * Subclasses of UEventObserver should override this method to handle
+ * UEvents.
+ */
+ public abstract void onUEvent(UEvent event);
/**
* Representation of a UEvent.
*/
- static public class UEvent {
+ public static final class UEvent {
// collection of key=value pairs parsed from the uevent message
- public HashMap<String,String> mMap = new HashMap<String,String>();
+ private final HashMap<String,String> mMap = new HashMap<String,String>();
public UEvent(String message) {
int offset = 0;
@@ -79,20 +144,20 @@ public abstract class UEventObserver {
}
}
- private static UEventThread sThread;
- private static boolean sThreadStarted = false;
-
- private static class UEventThread extends Thread {
+ private static final class UEventThread extends Thread {
/** Many to many mapping of string match to observer.
* Multimap would be better, but not available in android, so use
* an ArrayList where even elements are the String match and odd
* elements the corresponding UEventObserver observer */
- private ArrayList<Object> mObservers = new ArrayList<Object>();
-
- UEventThread() {
+ private final ArrayList<Object> mKeysAndObservers = new ArrayList<Object>();
+
+ private final ArrayList<UEventObserver> mTempObserversToSignal =
+ new ArrayList<UEventObserver>();
+
+ public UEventThread() {
super("UEventObserver");
}
-
+
public void run() {
native_setup();
@@ -101,91 +166,54 @@ public abstract class UEventObserver {
while (true) {
len = next_event(buffer);
if (len > 0) {
- String bufferStr = new String(buffer, 0, len); // easier to search a String
- synchronized (mObservers) {
- for (int i = 0; i < mObservers.size(); i += 2) {
- if (bufferStr.indexOf((String)mObservers.get(i)) != -1) {
- ((UEventObserver)mObservers.get(i+1))
- .onUEvent(new UEvent(bufferStr));
- }
- }
+ sendEvent(new String(buffer, 0, len));
+ }
+ }
+ }
+
+ private void sendEvent(String message) {
+ synchronized (mKeysAndObservers) {
+ final int N = mKeysAndObservers.size();
+ for (int i = 0; i < N; i += 2) {
+ final String key = (String)mKeysAndObservers.get(i);
+ if (message.indexOf(key) != -1) {
+ final UEventObserver observer =
+ (UEventObserver)mKeysAndObservers.get(i + 1);
+ mTempObserversToSignal.add(observer);
}
}
}
+
+ if (!mTempObserversToSignal.isEmpty()) {
+ final UEvent event = new UEvent(message);
+ final int N = mTempObserversToSignal.size();
+ for (int i = 0; i < N; i++) {
+ final UEventObserver observer = mTempObserversToSignal.get(i);
+ observer.onUEvent(event);
+ }
+ mTempObserversToSignal.clear();
+ }
}
+
public void addObserver(String match, UEventObserver observer) {
- synchronized(mObservers) {
- mObservers.add(match);
- mObservers.add(observer);
+ synchronized (mKeysAndObservers) {
+ mKeysAndObservers.add(match);
+ mKeysAndObservers.add(observer);
}
}
+
/** Removes every key/value pair where value=observer from mObservers */
public void removeObserver(UEventObserver observer) {
- synchronized(mObservers) {
- boolean found = true;
- while (found) {
- found = false;
- for (int i = 0; i < mObservers.size(); i += 2) {
- if (mObservers.get(i+1) == observer) {
- mObservers.remove(i+1);
- mObservers.remove(i);
- found = true;
- break;
- }
+ synchronized (mKeysAndObservers) {
+ for (int i = 0; i < mKeysAndObservers.size(); ) {
+ if (mKeysAndObservers.get(i + 1) == observer) {
+ mKeysAndObservers.remove(i + 1);
+ mKeysAndObservers.remove(i);
+ } else {
+ i += 2;
}
}
}
}
}
-
- private static native void native_setup();
- private static native int next_event(byte[] buffer);
-
- private static final synchronized void ensureThreadStarted() {
- if (sThreadStarted == false) {
- sThread = new UEventThread();
- sThread.start();
- sThreadStarted = true;
- }
- }
-
- /**
- * Begin observation of UEvent's.<p>
- * This method will cause the UEvent thread to start if this is the first
- * invocation of startObserving in this process.<p>
- * Once called, the UEvent thread will call onUEvent() when an incoming
- * UEvent matches the specified string.<p>
- * This method can be called multiple times to register multiple matches.
- * Only one call to stopObserving is required even with multiple registered
- * matches.
- * @param match A substring of the UEvent to match. Use "" to match all
- * UEvent's
- */
- public final synchronized void startObserving(String match) {
- ensureThreadStarted();
- sThread.addObserver(match, this);
- }
-
- /**
- * End observation of UEvent's.<p>
- * This process's UEvent thread will never call onUEvent() on this
- * UEventObserver after this call. Repeated calls have no effect.
- */
- public final synchronized void stopObserving() {
- sThread.removeObserver(this);
- }
-
- /**
- * Subclasses of UEventObserver should override this method to handle
- * UEvents.
- */
- public abstract void onUEvent(UEvent event);
-
- protected void finalize() throws Throwable {
- try {
- stopObserving();
- } finally {
- super.finalize();
- }
- }
}
diff --git a/core/java/android/os/UserHandle.aidl b/core/java/android/os/UserHandle.aidl
new file mode 100644
index 0000000..4892d32
--- /dev/null
+++ b/core/java/android/os/UserHandle.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.os;
+
+parcelable UserHandle;
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
new file mode 100644
index 0000000..cc96152
--- /dev/null
+++ b/core/java/android/os/UserHandle.java
@@ -0,0 +1,260 @@
+/*
+ * 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.os;
+
+/**
+ * Representation of a user on the device.
+ */
+public final class UserHandle implements Parcelable {
+ /**
+ * @hide Range of uids allocated for a user.
+ */
+ public static final int PER_USER_RANGE = 100000;
+
+ /** @hide A user id to indicate all users on the device */
+ public static final int USER_ALL = -1;
+
+ /** @hide A user handle to indicate all users on the device */
+ public static final UserHandle ALL = new UserHandle(USER_ALL);
+
+ /** @hide A user id to indicate the currently active user */
+ public static final int USER_CURRENT = -2;
+
+ /** @hide A user handle to indicate the current user of the device */
+ public static final UserHandle CURRENT = new UserHandle(USER_CURRENT);
+
+ /** @hide A user id to indicate that we would like to send to the current
+ * user, but if this is calling from a user process then we will send it
+ * to the caller's user instead of failing wiht a security exception */
+ public static final int USER_CURRENT_OR_SELF = -3;
+
+ /** @hide A user handle to indicate that we would like to send to the current
+ * user, but if this is calling from a user process then we will send it
+ * to the caller's user instead of failing wiht a security exception */
+ public static final UserHandle CURRENT_OR_SELF = new UserHandle(USER_CURRENT_OR_SELF);
+
+ /** @hide An undefined user id */
+ public static final int USER_NULL = -10000;
+
+ /** @hide A user id constant to indicate the "owner" user of the device */
+ public static final int USER_OWNER = 0;
+
+ /** @hide A user handle to indicate the primary/owner user of the device */
+ public static final UserHandle OWNER = new UserHandle(USER_OWNER);
+
+ /**
+ * @hide Enable multi-user related side effects. Set this to false if
+ * there are problems with single user use-cases.
+ */
+ public static final boolean MU_ENABLED = true;
+
+ final int mHandle;
+
+ /**
+ * Checks to see if the user id is the same for the two uids, i.e., they belong to the same
+ * user.
+ * @hide
+ */
+ public static final boolean isSameUser(int uid1, int uid2) {
+ return getUserId(uid1) == getUserId(uid2);
+ }
+
+ /**
+ * Checks to see if both uids are referring to the same app id, ignoring the user id part of the
+ * uids.
+ * @param uid1 uid to compare
+ * @param uid2 other uid to compare
+ * @return whether the appId is the same for both uids
+ * @hide
+ */
+ public static final boolean isSameApp(int uid1, int uid2) {
+ return getAppId(uid1) == getAppId(uid2);
+ }
+
+ /** @hide */
+ public static final boolean isIsolated(int uid) {
+ if (uid > 0) {
+ final int appId = getAppId(uid);
+ return appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID;
+ } else {
+ return false;
+ }
+ }
+
+ /** @hide */
+ public static boolean isApp(int uid) {
+ if (uid > 0) {
+ final int appId = getAppId(uid);
+ return appId >= Process.FIRST_APPLICATION_UID && appId <= Process.LAST_APPLICATION_UID;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the user id for a given uid.
+ * @hide
+ */
+ public static final int getUserId(int uid) {
+ if (MU_ENABLED) {
+ return uid / PER_USER_RANGE;
+ } else {
+ return 0;
+ }
+ }
+
+ /** @hide */
+ public static final int getCallingUserId() {
+ return getUserId(Binder.getCallingUid());
+ }
+
+ /**
+ * Returns the uid that is composed from the userId and the appId.
+ * @hide
+ */
+ public static final int getUid(int userId, int appId) {
+ if (MU_ENABLED) {
+ return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
+ } else {
+ return appId;
+ }
+ }
+
+ /**
+ * Returns the app id (or base uid) for a given uid, stripping out the user id from it.
+ * @hide
+ */
+ public static final int getAppId(int uid) {
+ return uid % PER_USER_RANGE;
+ }
+
+ /**
+ * Returns the shared app gid for a given uid or appId.
+ * @hide
+ */
+ public static final int getSharedAppGid(int id) {
+ return Process.FIRST_SHARED_APPLICATION_GID + (id % PER_USER_RANGE)
+ - Process.FIRST_APPLICATION_UID;
+ }
+
+ /**
+ * Returns the user id of the current process
+ * @return user id of the current process
+ * @hide
+ */
+ public static final int myUserId() {
+ return getUserId(Process.myUid());
+ }
+
+ /** @hide */
+ public UserHandle(int h) {
+ mHandle = h;
+ }
+
+ /** @hide */
+ public int getIdentifier() {
+ return mHandle;
+ }
+
+ @Override
+ public String toString() {
+ return "UserHandle{" + mHandle + "}";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ try {
+ if (obj != null) {
+ UserHandle other = (UserHandle)obj;
+ return mHandle == other.mHandle;
+ }
+ } catch (ClassCastException e) {
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mHandle;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mHandle);
+ }
+
+ /**
+ * Write a UserHandle to a Parcel, handling null pointers. Must be
+ * read with {@link #readFromParcel(Parcel)}.
+ *
+ * @param h The UserHandle to be written.
+ * @param out The Parcel in which the UserHandle will be placed.
+ *
+ * @see #readFromParcel(Parcel)
+ */
+ public static void writeToParcel(UserHandle h, Parcel out) {
+ if (h != null) {
+ h.writeToParcel(out, 0);
+ } else {
+ out.writeInt(USER_NULL);
+ }
+ }
+
+ /**
+ * Read a UserHandle from a Parcel that was previously written
+ * with {@link #writeToParcel(UserHandle, Parcel)}, returning either
+ * a null or new object as appropriate.
+ *
+ * @param in The Parcel from which to read the UserHandle
+ * @return Returns a new UserHandle matching the previously written
+ * object, or null if a null had been written.
+ *
+ * @see #writeToParcel(UserHandle, Parcel)
+ */
+ public static UserHandle readFromParcel(Parcel in) {
+ int h = in.readInt();
+ return h != USER_NULL ? new UserHandle(h) : null;
+ }
+
+ public static final Parcelable.Creator<UserHandle> CREATOR
+ = new Parcelable.Creator<UserHandle>() {
+ public UserHandle createFromParcel(Parcel in) {
+ return new UserHandle(in);
+ }
+
+ public UserHandle[] newArray(int size) {
+ return new UserHandle[size];
+ }
+ };
+
+ /**
+ * Instantiate a new UserHandle from the data in a Parcel that was
+ * previously written with {@link #writeToParcel(Parcel, int)}. Note that you
+ * must not use this with data written by
+ * {@link #writeToParcel(UserHandle, Parcel)} since it is not possible
+ * to handle a null UserHandle here.
+ *
+ * @param in The Parcel containing the previously written UserHandle,
+ * positioned at the location in the buffer where it was written.
+ */
+ public UserHandle(Parcel in) {
+ mHandle = in.readInt();
+ }
+}
diff --git a/core/java/android/os/UserId.java b/core/java/android/os/UserId.java
deleted file mode 100644
index 8bf6c6e..0000000
--- a/core/java/android/os/UserId.java
+++ /dev/null
@@ -1,116 +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.os;
-
-/**
- * @hide
- */
-public final class UserId {
- /**
- * Range of IDs allocated for a user.
- *
- * @hide
- */
- public static final int PER_USER_RANGE = 100000;
-
- public static final int USER_ALL = -1;
-
- /**
- * Enable multi-user related side effects. Set this to false if there are problems with single
- * user usecases.
- * */
- public static final boolean MU_ENABLED = true;
-
- /**
- * Checks to see if the user id is the same for the two uids, i.e., they belong to the same
- * user.
- * @hide
- */
- public static final boolean isSameUser(int uid1, int uid2) {
- return getUserId(uid1) == getUserId(uid2);
- }
-
- /**
- * Checks to see if both uids are referring to the same app id, ignoring the user id part of the
- * uids.
- * @param uid1 uid to compare
- * @param uid2 other uid to compare
- * @return whether the appId is the same for both uids
- * @hide
- */
- public static final boolean isSameApp(int uid1, int uid2) {
- return getAppId(uid1) == getAppId(uid2);
- }
-
- public static final boolean isIsolated(int uid) {
- uid = getAppId(uid);
- return uid >= Process.FIRST_ISOLATED_UID && uid <= Process.LAST_ISOLATED_UID;
- }
-
- public static boolean isApp(int uid) {
- if (uid > 0) {
- uid = UserId.getAppId(uid);
- return uid >= Process.FIRST_APPLICATION_UID && uid <= Process.LAST_APPLICATION_UID;
- } else {
- return false;
- }
- }
-
- /**
- * Returns the user id for a given uid.
- * @hide
- */
- public static final int getUserId(int uid) {
- if (MU_ENABLED) {
- return uid / PER_USER_RANGE;
- } else {
- return 0;
- }
- }
-
- public static final int getCallingUserId() {
- return getUserId(Binder.getCallingUid());
- }
-
- /**
- * Returns the uid that is composed from the userId and the appId.
- * @hide
- */
- public static final int getUid(int userId, int appId) {
- if (MU_ENABLED) {
- return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
- } else {
- return appId;
- }
- }
-
- /**
- * Returns the app id (or base uid) for a given uid, stripping out the user id from it.
- * @hide
- */
- public static final int getAppId(int uid) {
- return uid % PER_USER_RANGE;
- }
-
- /**
- * Returns the user id of the current process
- * @return user id of the current process
- */
- public static final int myUserId() {
- return getUserId(Process.myUid());
- }
-}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
new file mode 100644
index 0000000..96c96d7
--- /dev/null
+++ b/core/java/android/os/UserManager.java
@@ -0,0 +1,299 @@
+/*
+ * 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.os;
+
+import com.android.internal.R;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.content.res.Resources;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Manages users and user details on a multi-user system.
+ */
+public class UserManager {
+
+ private static String TAG = "UserManager";
+ private final IUserManager mService;
+ private final Context mContext;
+
+ /** @hide */
+ public UserManager(Context context, IUserManager service) {
+ mService = service;
+ mContext = context;
+ }
+
+ /**
+ * Returns whether the system supports multiple users.
+ * @return true if multiple users can be created, false if it is a single user device.
+ * @hide
+ */
+ public static boolean supportsMultipleUsers() {
+ return getMaxSupportedUsers() > 1;
+ }
+
+ /**
+ * Returns the user handle for the user that this application is running for.
+ * @return the user handle of the user making this call.
+ * @hide
+ * */
+ public int getUserHandle() {
+ return UserHandle.myUserId();
+ }
+
+ /**
+ * Returns the user name of the user making this call. This call is only
+ * available to applications on the system image; it requires the
+ * MANAGE_USERS permission.
+ * @return the user name
+ */
+ public String getUserName() {
+ try {
+ return mService.getUserInfo(getUserHandle()).name;
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get user name", re);
+ return "";
+ }
+ }
+
+ /**
+ * Used to determine whether the user making this call is subject to
+ * teleportations.
+ * @return whether the user making this call is a goat
+ */
+ public boolean isUserAGoat() {
+ return false;
+ }
+
+ /**
+ * Returns the UserInfo object describing a specific user.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * @param userHandle the user handle of the user whose information is being requested.
+ * @return the UserInfo object for a specific user.
+ * @hide
+ * */
+ public UserInfo getUserInfo(int userHandle) {
+ try {
+ return mService.getUserInfo(userHandle);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get user info", re);
+ return null;
+ }
+ }
+
+ /**
+ * Creates a user with the specified name and options.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ *
+ * @param name the user's name
+ * @param flags flags that identify the type of user and other properties.
+ * @see UserInfo
+ *
+ * @return the UserInfo object for the created user, or null if the user could not be created.
+ * @hide
+ */
+ public UserInfo createUser(String name, int flags) {
+ try {
+ return mService.createUser(name, flags);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not create a user", re);
+ return null;
+ }
+ }
+
+ /**
+ * Returns information for all users on this device.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * @return the list of users that were created.
+ * @hide
+ */
+ public List<UserInfo> getUsers() {
+ try {
+ return mService.getUsers(false);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get user list", re);
+ return null;
+ }
+ }
+
+ /**
+ * Returns information for all users on this device.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * @param excludeDying specify if the list should exclude users being removed.
+ * @return the list of users that were created.
+ * @hide
+ */
+ public List<UserInfo> getUsers(boolean excludeDying) {
+ try {
+ return mService.getUsers(excludeDying);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get user list", re);
+ return null;
+ }
+ }
+
+ /**
+ * Removes a user and all associated data.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * @param userHandle the integer handle of the user, where 0 is the primary user.
+ * @hide
+ */
+ public boolean removeUser(int userHandle) {
+ try {
+ return mService.removeUser(userHandle);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not remove user ", re);
+ return false;
+ }
+ }
+
+ /**
+ * Updates the user's name.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ *
+ * @param userHandle the user's integer handle
+ * @param name the new name for the user
+ * @hide
+ */
+ public void setUserName(int userHandle, String name) {
+ try {
+ mService.setUserName(userHandle, name);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not set the user name ", re);
+ }
+ }
+
+ /**
+ * Sets the user's photo.
+ * @param userHandle the user for whom to change the photo.
+ * @param icon the bitmap to set as the photo.
+ * @hide
+ */
+ public void setUserIcon(int userHandle, Bitmap icon) {
+ try {
+ mService.setUserIcon(userHandle, icon);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not set the user icon ", re);
+ }
+ }
+
+ /**
+ * Returns a file descriptor for the user's photo. PNG data can be read from this file.
+ * @param userHandle the user whose photo we want to read.
+ * @return a {@link Bitmap} of the user's photo, or null if there's no photo.
+ * @hide
+ */
+ public Bitmap getUserIcon(int userHandle) {
+ try {
+ return mService.getUserIcon(userHandle);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get the user icon ", re);
+ return null;
+ }
+ }
+
+ /**
+ * Enable or disable the use of a guest account. If disabled, the existing guest account
+ * will be wiped.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * @param enable whether to enable a guest account.
+ * @hide
+ */
+ public void setGuestEnabled(boolean enable) {
+ try {
+ mService.setGuestEnabled(enable);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not change guest account availability to " + enable);
+ }
+ }
+
+ /**
+ * Checks if a guest user is enabled for this device.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * @return whether a guest user is enabled
+ * @hide
+ */
+ public boolean isGuestEnabled() {
+ try {
+ return mService.isGuestEnabled();
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not retrieve guest enabled state");
+ return false;
+ }
+ }
+
+ /**
+ * Wipes all the data for a user, but doesn't remove the user.
+ * Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * @param userHandle
+ * @hide
+ */
+ public void wipeUser(int userHandle) {
+ try {
+ mService.wipeUser(userHandle);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not wipe user " + userHandle);
+ }
+ }
+
+ /**
+ * Returns the maximum number of users that can be created on this device. A return value
+ * of 1 means that it is a single user device.
+ * @hide
+ * @return a value greater than or equal to 1
+ */
+ public static int getMaxSupportedUsers() {
+ return SystemProperties.getInt("fw.max_users",
+ Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers));
+ }
+
+ /**
+ * Returns a serial number on this device for a given userHandle. User handles can be recycled
+ * when deleting and creating users, but serial numbers are not reused until the device is wiped.
+ * @param userHandle
+ * @return a serial number associated with that user, or -1 if the userHandle is not valid.
+ * @hide
+ */
+ public int getUserSerialNumber(int userHandle) {
+ try {
+ return mService.getUserSerialNumber(userHandle);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get serial number for user " + userHandle);
+ }
+ return -1;
+ }
+
+ /**
+ * Returns a userHandle on this device for a given user serial number. User handles can be
+ * recycled when deleting and creating users, but serial numbers are not reused until the device
+ * is wiped.
+ * @param userSerialNumber
+ * @return the userHandle associated with that user serial number, or -1 if the serial number
+ * is not valid.
+ * @hide
+ */
+ public int getUserHandle(int userSerialNumber) {
+ try {
+ return mService.getUserHandle(userSerialNumber);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get userHandle for user " + userSerialNumber);
+ }
+ return -1;
+ }
+}
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index 287c136..ba77df7 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1,5 +1,9 @@
package android.os;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+
/**
* Describes the source of some work that may be done by someone else.
* Currently the public representation of what a work source is is not
@@ -76,6 +80,20 @@ public class WorkSource implements Parcelable {
mNum = 0;
}
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof WorkSource && !diff((WorkSource)o);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ for (int i = 0; i < mNum; i++) {
+ result = ((result << 4) | (result >>> 28)) ^ mUids[i];
+ }
+ return result;
+ }
+
/**
* Compare this WorkSource with another.
* @param other The WorkSource to compare against.
@@ -299,6 +317,20 @@ public class WorkSource implements Parcelable {
dest.writeIntArray(mUids);
}
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("{WorkSource: uids=[");
+ for (int i = 0; i < mNum; i++) {
+ if (i != 0) {
+ result.append(", ");
+ }
+ result.append(mUids[i]);
+ }
+ result.append("]}");
+ return result.toString();
+ }
+
public static final Parcelable.Creator<WorkSource> CREATOR
= new Parcelable.Creator<WorkSource>() {
public WorkSource createFromParcel(Parcel in) {
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index ab64866..fc18617 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -489,13 +489,14 @@ public interface IMountService extends IInterface {
* IObbActionListener to inform it of the terminal state of the
* call.
*/
- public void mountObb(String filename, String key, IObbActionListener token, int nonce)
- throws RemoteException {
+ public void mountObb(String rawPath, String canonicalPath, String key,
+ IObbActionListener token, int nonce) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
- _data.writeString(filename);
+ _data.writeString(rawPath);
+ _data.writeString(canonicalPath);
_data.writeString(key);
_data.writeStrongBinder((token != null ? token.asBinder() : null));
_data.writeInt(nonce);
@@ -514,13 +515,14 @@ public interface IMountService extends IInterface {
* IObbActionListener to inform it of the terminal state of the
* call.
*/
- public void unmountObb(String filename, boolean force, IObbActionListener token,
- int nonce) throws RemoteException {
+ public void unmountObb(
+ String rawPath, boolean force, IObbActionListener token, int nonce)
+ throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
- _data.writeString(filename);
+ _data.writeString(rawPath);
_data.writeInt((force ? 1 : 0));
_data.writeStrongBinder((token != null ? token.asBinder() : null));
_data.writeInt(nonce);
@@ -536,13 +538,13 @@ public interface IMountService extends IInterface {
* Checks whether the specified Opaque Binary Blob (OBB) is mounted
* somewhere.
*/
- public boolean isObbMounted(String filename) throws RemoteException {
+ public boolean isObbMounted(String rawPath) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
boolean _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
- _data.writeString(filename);
+ _data.writeString(rawPath);
mRemote.transact(Stub.TRANSACTION_isObbMounted, _data, _reply, 0);
_reply.readException();
_result = 0 != _reply.readInt();
@@ -556,13 +558,13 @@ public interface IMountService extends IInterface {
/**
* Gets the path to the mounted Opaque Binary Blob (OBB).
*/
- public String getMountedObbPath(String filename) throws RemoteException {
+ public String getMountedObbPath(String rawPath) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
- _data.writeString(filename);
+ _data.writeString(rawPath);
mRemote.transact(Stub.TRANSACTION_getMountedObbPath, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
@@ -677,15 +679,15 @@ public interface IMountService extends IInterface {
return _result;
}
- public Parcelable[] getVolumeList() throws RemoteException {
+ public StorageVolume[] getVolumeList() throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
- Parcelable[] _result;
+ StorageVolume[] _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0);
_reply.readException();
- _result = _reply.readParcelableArray(StorageVolume.class.getClassLoader());
+ _result = _reply.createTypedArray(StorageVolume.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
@@ -1042,15 +1044,14 @@ public interface IMountService extends IInterface {
}
case TRANSACTION_mountObb: {
data.enforceInterface(DESCRIPTOR);
- String filename;
- filename = data.readString();
- String key;
- key = data.readString();
+ final String rawPath = data.readString();
+ final String canonicalPath = data.readString();
+ final String key = data.readString();
IObbActionListener observer;
observer = IObbActionListener.Stub.asInterface(data.readStrongBinder());
int nonce;
nonce = data.readInt();
- mountObb(filename, key, observer, nonce);
+ mountObb(rawPath, canonicalPath, key, observer, nonce);
reply.writeNoException();
return true;
}
@@ -1119,9 +1120,9 @@ public interface IMountService extends IInterface {
}
case TRANSACTION_getVolumeList: {
data.enforceInterface(DESCRIPTOR);
- Parcelable[] result = getVolumeList();
+ StorageVolume[] result = getVolumeList();
reply.writeNoException();
- reply.writeParcelableArray(result, 0);
+ reply.writeTypedArray(result, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
return true;
}
case TRANSACTION_getSecureContainerFilesystemPath: {
@@ -1194,7 +1195,7 @@ public interface IMountService extends IInterface {
/**
* Gets the path to the mounted Opaque Binary Blob (OBB).
*/
- public String getMountedObbPath(String filename) throws RemoteException;
+ public String getMountedObbPath(String rawPath) throws RemoteException;
/**
* Gets an Array of currently known secure container IDs
@@ -1220,7 +1221,7 @@ public interface IMountService extends IInterface {
* Checks whether the specified Opaque Binary Blob (OBB) is mounted
* somewhere.
*/
- public boolean isObbMounted(String filename) throws RemoteException;
+ public boolean isObbMounted(String rawPath) throws RemoteException;
/*
* Returns true if the specified container is mounted
@@ -1243,8 +1244,8 @@ public interface IMountService extends IInterface {
* MountService will call back to the supplied IObbActionListener to inform
* it of the terminal state of the call.
*/
- public void mountObb(String filename, String key, IObbActionListener token, int nonce)
- throws RemoteException;
+ public void mountObb(String rawPath, String canonicalPath, String key,
+ IObbActionListener token, int nonce) throws RemoteException;
/*
* Mount a secure container with the specified key and owner UID. Returns an
@@ -1287,7 +1288,7 @@ public interface IMountService extends IInterface {
* MountService will call back to the supplied IObbActionListener to inform
* it of the terminal state of the call.
*/
- public void unmountObb(String filename, boolean force, IObbActionListener token, int nonce)
+ public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce)
throws RemoteException;
/*
@@ -1358,7 +1359,7 @@ public interface IMountService extends IInterface {
/**
* Returns list of all mountable volumes.
*/
- public Parcelable[] getVolumeList() throws RemoteException;
+ public StorageVolume[] getVolumeList() throws RemoteException;
/**
* Gets the path on the filesystem for the ASEC container itself.
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 8a20a6e..862a95c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,6 +16,8 @@
package android.os.storage;
+import android.app.NotificationManager;
+import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
@@ -26,6 +28,10 @@ import android.os.ServiceManager;
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -285,6 +291,11 @@ public class StorageManager
}
}
+ /** {@hide} */
+ public static StorageManager from(Context context) {
+ return (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+ }
+
/**
* Constructs a StorageManager object through which an application can
* can communicate with the systems mount service.
@@ -436,25 +447,23 @@ public class StorageManager
* That is, shared UID applications can attempt to mount any other
* application's OBB that shares its UID.
*
- * @param filename the path to the OBB file
+ * @param rawPath the path to the OBB file
* @param key secret used to encrypt the OBB; may be <code>null</code> if no
* encryption was used on the OBB.
* @param listener will receive the success or failure of the operation
* @return whether the mount call was successfully queued or not
*/
- public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) {
- if (filename == null) {
- throw new IllegalArgumentException("filename cannot be null");
- }
-
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
- }
+ public boolean mountObb(String rawPath, String key, OnObbStateChangeListener listener) {
+ Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+ Preconditions.checkNotNull(listener, "listener cannot be null");
try {
+ final String canonicalPath = new File(rawPath).getCanonicalPath();
final int nonce = mObbActionListener.addListener(listener);
- mMountService.mountObb(filename, key, mObbActionListener, nonce);
+ mMountService.mountObb(rawPath, canonicalPath, key, mObbActionListener, nonce);
return true;
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed to resolve path: " + rawPath, e);
} catch (RemoteException e) {
Log.e(TAG, "Failed to mount OBB", e);
}
@@ -476,24 +485,19 @@ public class StorageManager
* application's OBB that shares its UID.
* <p>
*
- * @param filename path to the OBB file
+ * @param rawPath path to the OBB file
* @param force whether to kill any programs using this in order to unmount
* it
* @param listener will receive the success or failure of the operation
* @return whether the unmount call was successfully queued or not
*/
- public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener) {
- if (filename == null) {
- throw new IllegalArgumentException("filename cannot be null");
- }
-
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
- }
+ public boolean unmountObb(String rawPath, boolean force, OnObbStateChangeListener listener) {
+ Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+ Preconditions.checkNotNull(listener, "listener cannot be null");
try {
final int nonce = mObbActionListener.addListener(listener);
- mMountService.unmountObb(filename, force, mObbActionListener, nonce);
+ mMountService.unmountObb(rawPath, force, mObbActionListener, nonce);
return true;
} catch (RemoteException e) {
Log.e(TAG, "Failed to mount OBB", e);
@@ -505,16 +509,14 @@ public class StorageManager
/**
* Check whether an Opaque Binary Blob (OBB) is mounted or not.
*
- * @param filename path to OBB image
+ * @param rawPath path to OBB image
* @return true if OBB is mounted; false if not mounted or on error
*/
- public boolean isObbMounted(String filename) {
- if (filename == null) {
- throw new IllegalArgumentException("filename cannot be null");
- }
+ public boolean isObbMounted(String rawPath) {
+ Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
try {
- return mMountService.isObbMounted(filename);
+ return mMountService.isObbMounted(rawPath);
} catch (RemoteException e) {
Log.e(TAG, "Failed to check if OBB is mounted", e);
}
@@ -527,17 +529,15 @@ public class StorageManager
* give you the path to where you can obtain access to the internals of the
* OBB.
*
- * @param filename path to OBB image
+ * @param rawPath path to OBB image
* @return absolute path to mounted OBB image data or <code>null</code> if
* not mounted or exception encountered trying to read status
*/
- public String getMountedObbPath(String filename) {
- if (filename == null) {
- throw new IllegalArgumentException("filename cannot be null");
- }
+ public String getMountedObbPath(String rawPath) {
+ Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
try {
- return mMountService.getMountedObbPath(filename);
+ return mMountService.getMountedObbPath(rawPath);
} catch (RemoteException e) {
Log.e(TAG, "Failed to find mounted path for OBB", e);
}
@@ -594,4 +594,20 @@ public class StorageManager
}
return paths;
}
+
+ /** {@hide} */
+ public StorageVolume getPrimaryVolume() {
+ return getPrimaryVolume(getVolumeList());
+ }
+
+ /** {@hide} */
+ public static StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
+ for (StorageVolume volume : volumes) {
+ if (volume.isPrimary()) {
+ return volume;
+ }
+ }
+ Log.w(TAG, "No primary storage defined");
+ return null;
+ }
}
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 79c8f3b..177a955 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -19,53 +19,69 @@ package android.os.storage;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.io.File;
/**
- * A class representing a storage volume
+ * Description of a storage volume and its capabilities, including the
+ * filesystem path where it may be mounted.
+ *
* @hide
*/
public class StorageVolume implements Parcelable {
- //private static final String TAG = "StorageVolume";
+ // TODO: switch to more durable token
+ private int mStorageId;
- private final String mPath;
+ private final File mPath;
private final int mDescriptionId;
+ private final boolean mPrimary;
private final boolean mRemovable;
private final boolean mEmulated;
private final int mMtpReserveSpace;
private final boolean mAllowMassStorage;
- private int mStorageId;
- // maximum file size for the storage, or zero for no limit
+ /** Maximum file size for the storage, or zero for no limit */
private final long mMaxFileSize;
+ /** When set, indicates exclusive ownership of this volume */
+ private final UserHandle mOwner;
// StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
// ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED,
// ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
- public StorageVolume(String path, int descriptionId, boolean removable,
- boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize) {
+ public StorageVolume(File path, int descriptionId, boolean primary, boolean removable,
+ boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize,
+ UserHandle owner) {
mPath = path;
mDescriptionId = descriptionId;
+ mPrimary = primary;
mRemovable = removable;
mEmulated = emulated;
mMtpReserveSpace = mtpReserveSpace;
mAllowMassStorage = allowMassStorage;
mMaxFileSize = maxFileSize;
+ mOwner = owner;
}
- // for parcelling only
- private StorageVolume(String path, int descriptionId, boolean removable,
- boolean emulated, int mtpReserveSpace, int storageId,
- boolean allowMassStorage, long maxFileSize) {
- mPath = path;
- mDescriptionId = descriptionId;
- mRemovable = removable;
- mEmulated = emulated;
- mMtpReserveSpace = mtpReserveSpace;
- mAllowMassStorage = allowMassStorage;
- mStorageId = storageId;
- mMaxFileSize = maxFileSize;
+ private StorageVolume(Parcel in) {
+ mStorageId = in.readInt();
+ mPath = new File(in.readString());
+ mDescriptionId = in.readInt();
+ mPrimary = in.readInt() != 0;
+ mRemovable = in.readInt() != 0;
+ mEmulated = in.readInt() != 0;
+ mMtpReserveSpace = in.readInt();
+ mAllowMassStorage = in.readInt() != 0;
+ mMaxFileSize = in.readLong();
+ mOwner = in.readParcelable(null);
+ }
+
+ public static StorageVolume fromTemplate(StorageVolume template, File path, UserHandle owner) {
+ return new StorageVolume(path, template.mDescriptionId, template.mPrimary,
+ template.mRemovable, template.mEmulated, template.mMtpReserveSpace,
+ template.mAllowMassStorage, template.mMaxFileSize, owner);
}
/**
@@ -74,6 +90,10 @@ public class StorageVolume implements Parcelable {
* @return the mount path
*/
public String getPath() {
+ return mPath.toString();
+ }
+
+ public File getPathFile() {
return mPath;
}
@@ -90,6 +110,10 @@ public class StorageVolume implements Parcelable {
return mDescriptionId;
}
+ public boolean isPrimary() {
+ return mPrimary;
+ }
+
/**
* Returns true if the volume is removable.
*
@@ -161,6 +185,10 @@ public class StorageVolume implements Parcelable {
return mMaxFileSize;
}
+ public UserHandle getOwner() {
+ return mOwner;
+ }
+
@Override
public boolean equals(Object obj) {
if (obj instanceof StorageVolume && mPath != null) {
@@ -177,45 +205,49 @@ public class StorageVolume implements Parcelable {
@Override
public String toString() {
- return "StorageVolume [mAllowMassStorage=" + mAllowMassStorage + ", mDescriptionId="
- + mDescriptionId + ", mEmulated=" + mEmulated + ", mMaxFileSize=" + mMaxFileSize
- + ", mMtpReserveSpace=" + mMtpReserveSpace + ", mPath=" + mPath + ", mRemovable="
- + mRemovable + ", mStorageId=" + mStorageId + "]";
- }
-
- public static final Parcelable.Creator<StorageVolume> CREATOR =
- new Parcelable.Creator<StorageVolume>() {
+ final StringBuilder builder = new StringBuilder("StorageVolume [");
+ builder.append("mStorageId=").append(mStorageId);
+ builder.append(" mPath=").append(mPath);
+ builder.append(" mDescriptionId=").append(mDescriptionId);
+ builder.append(" mPrimary=").append(mPrimary);
+ builder.append(" mRemovable=").append(mRemovable);
+ builder.append(" mEmulated=").append(mEmulated);
+ builder.append(" mMtpReserveSpace=").append(mMtpReserveSpace);
+ builder.append(" mAllowMassStorage=").append(mAllowMassStorage);
+ builder.append(" mMaxFileSize=").append(mMaxFileSize);
+ builder.append(" mOwner=").append(mOwner);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ public static final Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() {
+ @Override
public StorageVolume createFromParcel(Parcel in) {
- String path = in.readString();
- int descriptionId = in.readInt();
- int removable = in.readInt();
- int emulated = in.readInt();
- int storageId = in.readInt();
- int mtpReserveSpace = in.readInt();
- int allowMassStorage = in.readInt();
- long maxFileSize = in.readLong();
- return new StorageVolume(path, descriptionId,
- removable == 1, emulated == 1, mtpReserveSpace,
- storageId, allowMassStorage == 1, maxFileSize);
+ return new StorageVolume(in);
}
+ @Override
public StorageVolume[] newArray(int size) {
return new StorageVolume[size];
}
};
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeString(mPath);
+ parcel.writeInt(mStorageId);
+ parcel.writeString(mPath.toString());
parcel.writeInt(mDescriptionId);
+ parcel.writeInt(mPrimary ? 1 : 0);
parcel.writeInt(mRemovable ? 1 : 0);
parcel.writeInt(mEmulated ? 1 : 0);
- parcel.writeInt(mStorageId);
parcel.writeInt(mMtpReserveSpace);
parcel.writeInt(mAllowMassStorage ? 1 : 0);
parcel.writeLong(mMaxFileSize);
+ parcel.writeParcelable(mOwner, flags);
}
}
diff --git a/core/java/android/preference/TwoStatePreference.java b/core/java/android/preference/TwoStatePreference.java
index d186b20..c649879 100644
--- a/core/java/android/preference/TwoStatePreference.java
+++ b/core/java/android/preference/TwoStatePreference.java
@@ -37,6 +37,7 @@ public abstract class TwoStatePreference extends Preference {
private CharSequence mSummaryOn;
private CharSequence mSummaryOff;
boolean mChecked;
+ private boolean mCheckedSet;
private boolean mSendClickAccessibilityEvent;
private boolean mDisableDependentsState;
@@ -74,11 +75,16 @@ public abstract class TwoStatePreference extends Preference {
* @param checked The checked state.
*/
public void setChecked(boolean checked) {
- if (mChecked != checked) {
+ // Always persist/notify the first time; don't assume the field's default of false.
+ final boolean changed = mChecked != checked;
+ if (changed || !mCheckedSet) {
mChecked = checked;
+ mCheckedSet = true;
persistBoolean(checked);
- notifyDependencyChange(shouldDisableDependents());
- notifyChanged();
+ if (changed) {
+ notifyDependencyChange(shouldDisableDependents());
+ notifyChanged();
+ }
}
}
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index a28585c..af6e88e9 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -467,6 +467,13 @@ public final class CalendarContract {
*
*/
public static final String ALLOWED_ATTENDEE_TYPES = "allowedAttendeeTypes";
+
+ /**
+ * Is this the primary calendar for this account. If this column is not explicitly set, the
+ * provider will return 1 if {@link Calendars#ACCOUNT_NAME} is equal to
+ * {@link Calendars#OWNER_ACCOUNT}.
+ */
+ public static final String IS_PRIMARY = "isPrimary";
}
/**
@@ -1206,6 +1213,14 @@ public final class CalendarContract {
public static final String ORGANIZER = "organizer";
/**
+ * Are we the organizer of this event. If this column is not explicitly set, the provider
+ * will return 1 if {@link #ORGANIZER} is equal to {@link Calendars#OWNER_ACCOUNT}.
+ * Column name.
+ * <P>Type: STRING</P>
+ */
+ public static final String IS_ORGANIZER = "isOrganizer";
+
+ /**
* Whether the user can invite others to the event. The
* GUESTS_CAN_INVITE_OTHERS is a setting that applies to an arbitrary
* guest, while CAN_INVITE_OTHERS indicates if the user can invite
@@ -1230,6 +1245,12 @@ public final class CalendarContract {
*/
public static final String CUSTOM_APP_URI = "customAppUri";
+ /**
+ * The UID for events added from the RFC 2445 iCalendar format.
+ * Column name.
+ * <P>Type: TEXT</P>
+ */
+ public static final String UID_2445 = "uid2445";
}
/**
@@ -1367,7 +1388,9 @@ public final class CalendarContract {
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, GUESTS_CAN_SEE_GUESTS);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CUSTOM_APP_PACKAGE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CUSTOM_APP_URI);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, UID_2445);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ORGANIZER);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, IS_ORGANIZER);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_SYNCED);
@@ -1571,6 +1594,7 @@ public final class CalendarContract {
* <li>{@link #GUESTS_CAN_SEE_GUESTS}</li>
* <li>{@link #CUSTOM_APP_PACKAGE}</li>
* <li>{@link #CUSTOM_APP_URI}</li>
+ * <li>{@link #UID_2445}</li>
* </ul>
* The following Events columns are writable only by a sync adapter
* <ul>
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 22b68bc..5dca67f 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -17,9 +17,6 @@
package android.provider;
-import com.android.internal.telephony.CallerInfo;
-import com.android.internal.telephony.PhoneConstants;
-
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -30,6 +27,9 @@ import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.DataUsageFeedback;
import android.text.TextUtils;
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.PhoneConstants;
+
/**
* The CallLog provider contains information about placed and received calls.
*/
@@ -59,6 +59,20 @@ public class CallLog {
Uri.parse("content://call_log/calls/filter");
/**
+ * Query parameter used to limit the number of call logs returned.
+ * <p>
+ * TYPE: integer
+ */
+ public static final String LIMIT_PARAM_KEY = "limit";
+
+ /**
+ * Query parameter used to specify the starting record to return.
+ * <p>
+ * TYPE: integer
+ */
+ public static final String OFFSET_PARAM_KEY = "offset";
+
+ /**
* An optional URI parameter which instructs the provider to allow the operation to be
* applied to voicemail records as well.
* <p>
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index e7b0579..5b49ba3 100755
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -37,6 +37,7 @@ import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Pair;
@@ -345,10 +346,10 @@ public final class ContactsContract {
* directory provider URIs by themselves. This level of indirection allows
* Contacts Provider to implement additional system-level features and
* optimizations. Access to Contacts Provider is protected by the
- * READ_CONTACTS permission, but access to the directory provider is not.
- * Therefore directory providers must reject requests coming from clients
- * other than the Contacts Provider itself. An easy way to prevent such
- * unauthorized access is to check the name of the calling package:
+ * READ_CONTACTS permission, but access to the directory provider is protected by
+ * BIND_DIRECTORY_SEARCH. This permission was introduced at the API level 17, for previous
+ * platform versions the provider should perform the following check to make sure the call
+ * is coming from the ContactsProvider:
* <pre>
* private boolean isCallerAllowed() {
* PackageManager pm = getContext().getPackageManager();
@@ -7658,6 +7659,54 @@ public final class ContactsContract {
public static final int MODE_LARGE = 3;
/**
+ * Constructs the QuickContacts intent with a view's rect.
+ * @hide
+ */
+ public static Intent composeQuickContactsIntent(Context context, View target, Uri lookupUri,
+ int mode, String[] excludeMimes) {
+ // Find location and bounds of target view, adjusting based on the
+ // assumed local density.
+ final float appScale = context.getResources().getCompatibilityInfo().applicationScale;
+ final int[] pos = new int[2];
+ target.getLocationOnScreen(pos);
+
+ final Rect rect = new Rect();
+ rect.left = (int) (pos[0] * appScale + 0.5f);
+ rect.top = (int) (pos[1] * appScale + 0.5f);
+ rect.right = (int) ((pos[0] + target.getWidth()) * appScale + 0.5f);
+ rect.bottom = (int) ((pos[1] + target.getHeight()) * appScale + 0.5f);
+
+ return composeQuickContactsIntent(context, rect, lookupUri, mode, excludeMimes);
+ }
+
+ /**
+ * Constructs the QuickContacts intent.
+ * @hide
+ */
+ public static Intent composeQuickContactsIntent(Context context, Rect target,
+ Uri lookupUri, int mode, String[] excludeMimes) {
+ // When launching from an Activiy, we don't want to start a new task, but otherwise
+ // we *must* start a new task. (Otherwise startActivity() would crash.)
+ Context actualContext = context;
+ while ((actualContext instanceof ContextWrapper)
+ && !(actualContext instanceof Activity)) {
+ actualContext = ((ContextWrapper) actualContext).getBaseContext();
+ }
+ final int intentFlags = (actualContext instanceof Activity)
+ ? Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
+ : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK;
+
+ // Launch pivot dialog through intent for now
+ final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags);
+
+ intent.setData(lookupUri);
+ intent.setSourceBounds(target);
+ intent.putExtra(EXTRA_MODE, mode);
+ intent.putExtra(EXTRA_EXCLUDE_MIMES, excludeMimes);
+ return intent;
+ }
+
+ /**
* Trigger a dialog that lists the various methods of interacting with
* the requested {@link Contacts} entry. This may be based on available
* {@link ContactsContract.Data} rows under that contact, and may also
@@ -7682,20 +7731,10 @@ public final class ContactsContract {
*/
public static void showQuickContact(Context context, View target, Uri lookupUri, int mode,
String[] excludeMimes) {
- // Find location and bounds of target view, adjusting based on the
- // assumed local density.
- final float appScale = context.getResources().getCompatibilityInfo().applicationScale;
- final int[] pos = new int[2];
- target.getLocationOnScreen(pos);
-
- final Rect rect = new Rect();
- rect.left = (int) (pos[0] * appScale + 0.5f);
- rect.top = (int) (pos[1] * appScale + 0.5f);
- rect.right = (int) ((pos[0] + target.getWidth()) * appScale + 0.5f);
- rect.bottom = (int) ((pos[1] + target.getHeight()) * appScale + 0.5f);
-
// Trigger with obtained rectangle
- showQuickContact(context, rect, lookupUri, mode, excludeMimes);
+ Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode,
+ excludeMimes);
+ context.startActivity(intent);
}
/**
@@ -7726,24 +7765,8 @@ public final class ContactsContract {
*/
public static void showQuickContact(Context context, Rect target, Uri lookupUri, int mode,
String[] excludeMimes) {
- // When launching from an Activiy, we don't want to start a new task, but otherwise
- // we *must* start a new task. (Otherwise startActivity() would crash.)
- Context actualContext = context;
- while ((actualContext instanceof ContextWrapper)
- && !(actualContext instanceof Activity)) {
- actualContext = ((ContextWrapper) actualContext).getBaseContext();
- }
- final int intentFlags = (actualContext instanceof Activity)
- ? Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
- : Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK;
-
- // Launch pivot dialog through intent for now
- final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags);
-
- intent.setData(lookupUri);
- intent.setSourceBounds(target);
- intent.putExtra(EXTRA_MODE, mode);
- intent.putExtra(EXTRA_EXCLUDE_MIMES, excludeMimes);
+ Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode,
+ excludeMimes);
context.startActivity(intent);
}
}
@@ -7900,6 +7923,16 @@ public final class ContactsContract {
"com.android.contacts.action.GET_MULTIPLE_PHONES";
/**
+ * A broadcast action which is sent when any change has been made to the profile, such
+ * as the profile name or the picture. A receiver must have
+ * the android.permission.READ_PROFILE permission.
+ *
+ * @hide
+ */
+ public static final String ACTION_PROFILE_CHANGED =
+ "android.provider.Contacts.PROFILE_CHANGED";
+
+ /**
* Used with {@link #SHOW_OR_CREATE_CONTACT} to force creating a new
* contact if no matching contact found. Otherwise, default behavior is
* to prompt user with dialog before creating.
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 79d0144..0e7ab52 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -120,7 +120,39 @@ public final class MediaStore {
*/
public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH =
"android.media.action.MEDIA_PLAY_FROM_SEARCH";
-
+
+ /**
+ * An intent to perform a search for readable media and automatically play content from the
+ * result when possible. This can be fired, for example, by the result of a voice recognition
+ * command to read a book or magazine.
+ * <p>
+ * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can
+ * contain any type of unstructured text search, like the name of a book or magazine, an author
+ * a genre, a publisher, or any combination of these.
+ * <p>
+ * Because this intent includes an open-ended unstructured search string, it makes the most
+ * 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.
+ */
+ public static final String INTENT_ACTION_TEXT_OPEN_FROM_SEARCH =
+ "android.media.action.TEXT_OPEN_FROM_SEARCH";
+
+ /**
+ * An intent to perform a search for video media and automatically play content from the
+ * result when possible. This can be fired, for example, by the result of a voice recognition
+ * command to play movies.
+ * <p>
+ * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can
+ * contain any type of unstructured video search, like the name of a movie, one or more actors,
+ * a genre, or any combination of these.
+ * <p>
+ * Because this intent includes an open-ended unstructured search string, it makes the most
+ * 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.
+ */
+ public static final String INTENT_ACTION_VIDEO_PLAY_FROM_SEARCH =
+ "android.media.action.VIDEO_PLAY_FROM_SEARCH";
+
/**
* The name of the Intent-extra used to define the artist
*/
@@ -173,6 +205,21 @@ public final class MediaStore {
public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA";
/**
+ * The name of the Intent action used to launch a camera in still image mode
+ * for use when the device is secured (e.g. with a pin, password, pattern,
+ * or face unlock). Applications responding to this intent must not expose
+ * any personal content like existing photos or videos on the device. The
+ * applications should be careful not to share any photo or video with other
+ * applications or internet. The activity should use {@link
+ * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display
+ * on top of the lock screen while secured. There is no activity stack when
+ * this flag is used, so launching more than one activity is strongly
+ * discouraged.
+ */
+ 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.
*/
public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA";
@@ -191,6 +238,28 @@ public final class MediaStore {
public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
/**
+ * Intent action that can be sent to have the camera application capture an image and return
+ * it when the device is secured (e.g. with a pin, password, pattern, or face unlock).
+ * Applications responding to this intent must not expose any personal content like existing
+ * photos or videos on the device. The applications should be careful not to share any photo
+ * or video with other applications or internet. The activity should use {@link
+ * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display on top of the
+ * lock screen while secured. There is no activity stack when this flag is used, so
+ * launching more than one activity is strongly discouraged.
+ * <p>
+ * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written.
+ * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap
+ * object in the extra field. This is useful for applications that only need a small image.
+ * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
+ * value of EXTRA_OUTPUT.
+ *
+ * @see #ACTION_IMAGE_CAPTURE
+ * @see #EXTRA_OUTPUT
+ */
+ public static final String ACTION_IMAGE_CAPTURE_SECURE =
+ "android.media.action.IMAGE_CAPTURE_SECURE";
+
+ /**
* Standard Intent action that can be sent to have the camera application
* capture a video and return it.
* <p>
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 74c0a97..2a8cf21 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -19,6 +19,7 @@ package android.provider;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.SearchManager;
+import android.app.WallpaperManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -32,16 +33,19 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.SQLException;
+import android.net.ConnectivityManager;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.BatteryManager;
import android.os.Bundle;
+import android.os.DropBoxManager;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
-import android.os.UserId;
+import android.os.UserHandle;
+import android.os.Build.VERSION_CODES;
import android.speech.tts.TextToSpeech;
import android.text.TextUtils;
import android.util.AndroidException;
@@ -214,6 +218,21 @@ public final class Settings {
"android.settings.BLUETOOTH_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of Wifi Displays.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_WIFI_DISPLAY_SETTINGS =
+ "android.settings.WIFI_DISPLAY_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of date and time.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -621,6 +640,25 @@ public final class Settings {
public static final String CALL_METHOD_GET_SECURE = "GET_secure";
/**
+ * @hide - Private call() method on SettingsProvider to read from 'global' table.
+ */
+ public static final String CALL_METHOD_GET_GLOBAL = "GET_global";
+
+ /**
+ * @hide - User handle argument extra to the fast-path call()-based requests
+ */
+ public static final String CALL_METHOD_USER_KEY = "_user";
+
+ /** @hide - Private call() method to write to 'system' table */
+ public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
+
+ /** @hide - Private call() method to write to 'secure' table */
+ public static final String CALL_METHOD_PUT_SECURE = "PUT_secure";
+
+ /** @hide - Private call() method to write to 'global' table */
+ public static final String CALL_METHOD_PUT_GLOBAL= "PUT_global";
+
+ /**
* Activity Extra: Limit available options in launched activity based on the given authority.
* <p>
* This can be passed as an extra field in an Activity Intent with one or more syncable content
@@ -640,7 +678,7 @@ public final class Settings {
public static final String AUTHORITY = "settings";
private static final String TAG = "Settings";
- private static final boolean LOCAL_LOGV = false || false;
+ private static final boolean LOCAL_LOGV = false;
public static class SettingNotFoundException extends AndroidException {
public SettingNotFoundException(String msg) {
@@ -693,33 +731,18 @@ public final class Settings {
// The method we'll call (or null, to not use) on the provider
// for the fast path of retrieving settings.
- private final String mCallCommand;
+ private final String mCallGetCommand;
+ private final String mCallSetCommand;
- public NameValueCache(String versionSystemProperty, Uri uri, String callCommand) {
+ public NameValueCache(String versionSystemProperty, Uri uri,
+ String getCommand, String setCommand) {
mVersionSystemProperty = versionSystemProperty;
mUri = uri;
- mCallCommand = callCommand;
+ mCallGetCommand = getCommand;
+ mCallSetCommand = setCommand;
}
- public String getString(ContentResolver cr, String name) {
- long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0);
-
- synchronized (this) {
- if (mValuesVersion != newValuesVersion) {
- if (LOCAL_LOGV) {
- Log.v(TAG, "invalidate [" + mUri.getLastPathSegment() + "]: current " +
- newValuesVersion + " != cached " + mValuesVersion);
- }
-
- mValues.clear();
- mValuesVersion = newValuesVersion;
- }
-
- if (mValues.containsKey(name)) {
- return mValues.get(name); // Could be null, that's OK -- negative caching
- }
- }
-
+ private IContentProvider lazyGetProvider(ContentResolver cr) {
IContentProvider cp = null;
synchronized (this) {
cp = mContentProvider;
@@ -727,18 +750,75 @@ public final class Settings {
cp = mContentProvider = cr.acquireProvider(mUri.getAuthority());
}
}
+ return cp;
+ }
+
+ public boolean putStringForUser(ContentResolver cr, String name, String value,
+ final int userHandle) {
+ try {
+ Bundle arg = new Bundle();
+ arg.putString(Settings.NameValueTable.VALUE, value);
+ arg.putInt(CALL_METHOD_USER_KEY, userHandle);
+ IContentProvider cp = lazyGetProvider(cr);
+ cp.call(mCallSetCommand, name, arg);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
+ return false;
+ }
+ return true;
+ }
+
+ public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
+ final boolean isSelf = (userHandle == UserHandle.myUserId());
+ if (isSelf) {
+ long newValuesVersion = SystemProperties.getLong(mVersionSystemProperty, 0);
+
+ // Our own user's settings data uses a client-side cache
+ synchronized (this) {
+ if (mValuesVersion != newValuesVersion) {
+ if (LOCAL_LOGV || false) {
+ Log.v(TAG, "invalidate [" + mUri.getLastPathSegment() + "]: current "
+ + newValuesVersion + " != cached " + mValuesVersion);
+ }
+
+ mValues.clear();
+ mValuesVersion = newValuesVersion;
+ }
+
+ if (mValues.containsKey(name)) {
+ return mValues.get(name); // Could be null, that's OK -- negative caching
+ }
+ }
+ } else {
+ if (LOCAL_LOGV) Log.v(TAG, "get setting for user " + userHandle
+ + " by user " + UserHandle.myUserId() + " so skipping cache");
+ }
+
+ IContentProvider cp = lazyGetProvider(cr);
// Try the fast path first, not using query(). If this
// fails (alternate Settings provider that doesn't support
// this interface?) then we fall back to the query/table
// interface.
- if (mCallCommand != null) {
+ if (mCallGetCommand != null) {
try {
- Bundle b = cp.call(mCallCommand, name, null);
+ Bundle args = null;
+ if (!isSelf) {
+ args = new Bundle();
+ args.putInt(CALL_METHOD_USER_KEY, userHandle);
+ }
+ Bundle b = cp.call(mCallGetCommand, name, args);
if (b != null) {
String value = b.getPairValue();
- synchronized (this) {
- mValues.put(name, value);
+ // Don't update our cache for reads of other users' data
+ if (isSelf) {
+ synchronized (this) {
+ mValues.put(name, value);
+ }
+ } else {
+ if (LOCAL_LOGV) Log.i(TAG, "call-query of user " + userHandle
+ + " by " + UserHandle.myUserId()
+ + " so not updating cache");
}
return value;
}
@@ -785,19 +865,23 @@ public final class Settings {
public static final class System extends NameValueTable {
public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version";
- // Populated lazily, guarded by class object:
- private static NameValueCache sNameValueCache = null;
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://" + AUTHORITY + "/system");
+
+ private static final NameValueCache sNameValueCache = new NameValueCache(
+ SYS_PROP_SETTING_VERSION,
+ CONTENT_URI,
+ CALL_METHOD_GET_SYSTEM,
+ CALL_METHOD_PUT_SYSTEM);
private static final HashSet<String> MOVED_TO_SECURE;
static {
MOVED_TO_SECURE = new HashSet<String>(30);
- MOVED_TO_SECURE.add(Secure.ADB_ENABLED);
MOVED_TO_SECURE.add(Secure.ANDROID_ID);
- MOVED_TO_SECURE.add(Secure.BLUETOOTH_ON);
- MOVED_TO_SECURE.add(Secure.DATA_ROAMING);
- MOVED_TO_SECURE.add(Secure.DEVICE_PROVISIONED);
MOVED_TO_SECURE.add(Secure.HTTP_PROXY);
- MOVED_TO_SECURE.add(Secure.INSTALL_NON_MARKET_APPS);
MOVED_TO_SECURE.add(Secure.LOCATION_PROVIDERS_ALLOWED);
MOVED_TO_SECURE.add(Secure.LOCK_BIOMETRIC_WEAK_FLAGS);
MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_ENABLED);
@@ -808,7 +892,6 @@ public final class Settings {
MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_LAST_UPDATE);
MOVED_TO_SECURE.add(Secure.PARENTAL_CONTROL_REDIRECT_URL);
MOVED_TO_SECURE.add(Secure.SETTINGS_CLASSNAME);
- MOVED_TO_SECURE.add(Secure.USB_MASS_STORAGE_ENABLED);
MOVED_TO_SECURE.add(Secure.USE_GOOGLE_MAIL);
MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
MOVED_TO_SECURE.add(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY);
@@ -827,23 +910,74 @@ public final class Settings {
MOVED_TO_SECURE.add(Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS);
}
+ private static final HashSet<String> MOVED_TO_GLOBAL;
+ static {
+ MOVED_TO_GLOBAL = new HashSet<String>();
+ // these were originally in system but migrated to secure in the past,
+ // so are duplicated in the Secure.* namespace
+ MOVED_TO_GLOBAL.add(Global.ADB_ENABLED);
+ MOVED_TO_GLOBAL.add(Global.BLUETOOTH_ON);
+ MOVED_TO_GLOBAL.add(Global.DATA_ROAMING);
+ MOVED_TO_GLOBAL.add(Global.DEVICE_PROVISIONED);
+ MOVED_TO_GLOBAL.add(Global.INSTALL_NON_MARKET_APPS);
+ MOVED_TO_GLOBAL.add(Global.USB_MASS_STORAGE_ENABLED);
+ MOVED_TO_GLOBAL.add(Global.HTTP_PROXY);
+
+ // these are moving directly from system to global
+ MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_ON);
+ MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_RADIOS);
+ MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+ MOVED_TO_GLOBAL.add(Settings.Global.AUTO_TIME);
+ MOVED_TO_GLOBAL.add(Settings.Global.AUTO_TIME_ZONE);
+ MOVED_TO_GLOBAL.add(Settings.Global.CAR_DOCK_SOUND);
+ MOVED_TO_GLOBAL.add(Settings.Global.CAR_UNDOCK_SOUND);
+ MOVED_TO_GLOBAL.add(Settings.Global.DESK_DOCK_SOUND);
+ MOVED_TO_GLOBAL.add(Settings.Global.DESK_UNDOCK_SOUND);
+ MOVED_TO_GLOBAL.add(Settings.Global.DOCK_SOUNDS_ENABLED);
+ MOVED_TO_GLOBAL.add(Settings.Global.LOCK_SOUND);
+ MOVED_TO_GLOBAL.add(Settings.Global.UNLOCK_SOUND);
+ MOVED_TO_GLOBAL.add(Settings.Global.LOW_BATTERY_SOUND);
+ MOVED_TO_GLOBAL.add(Settings.Global.POWER_SOUNDS_ENABLED);
+ MOVED_TO_GLOBAL.add(Settings.Global.STAY_ON_WHILE_PLUGGED_IN);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SLEEP_POLICY);
+ MOVED_TO_GLOBAL.add(Settings.Global.MODE_RINGER);
+ MOVED_TO_GLOBAL.add(Settings.Global.WINDOW_ANIMATION_SCALE);
+ MOVED_TO_GLOBAL.add(Settings.Global.TRANSITION_ANIMATION_SCALE);
+ MOVED_TO_GLOBAL.add(Settings.Global.ANIMATOR_DURATION_SCALE);
+ MOVED_TO_GLOBAL.add(Settings.Global.FANCY_IME_ANIMATIONS);
+ MOVED_TO_GLOBAL.add(Settings.Global.COMPATIBILITY_MODE);
+ MOVED_TO_GLOBAL.add(Settings.Global.EMERGENCY_TONE);
+ MOVED_TO_GLOBAL.add(Settings.Global.CALL_AUTO_RETRY);
+ MOVED_TO_GLOBAL.add(Settings.Global.DEBUG_APP);
+ 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);
+ }
+
/**
* Look up a name in the database.
* @param resolver to access the database with
* @param name to look up in the table
* @return the corresponding value, or null if not present
*/
- public synchronized static String getString(ContentResolver resolver, String name) {
+ public static String getString(ContentResolver resolver, String name) {
+ return getStringForUser(resolver, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static String getStringForUser(ContentResolver resolver, String name,
+ int userHandle) {
if (MOVED_TO_SECURE.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ " to android.provider.Settings.Secure, returning read-only value.");
- return Secure.getString(resolver, name);
+ return Secure.getStringForUser(resolver, name, userHandle);
}
- if (sNameValueCache == null) {
- sNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI,
- CALL_METHOD_GET_SYSTEM);
+ if (MOVED_TO_GLOBAL.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ + " to android.provider.Settings.Global, returning read-only value.");
+ return Global.getStringForUser(resolver, name, userHandle);
}
- return sNameValueCache.getString(resolver, name);
+ return sNameValueCache.getStringForUser(resolver, name, userHandle);
}
/**
@@ -854,12 +988,23 @@ public final class Settings {
* @return true if the value was set, false on database errors
*/
public static boolean putString(ContentResolver resolver, String name, String value) {
+ return putStringForUser(resolver, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putStringForUser(ContentResolver resolver, String name, String value,
+ int userHandle) {
if (MOVED_TO_SECURE.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ " to android.provider.Settings.Secure, value is unchanged.");
return false;
}
- return putString(resolver, CONTENT_URI, name, value);
+ if (MOVED_TO_GLOBAL.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ + " to android.provider.Settings.Global, value is unchanged.");
+ return false;
+ }
+ return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
}
/**
@@ -874,6 +1019,11 @@ public final class Settings {
+ " to android.provider.Settings.Secure, returning Secure URI.");
return Secure.getUriFor(Secure.CONTENT_URI, name);
}
+ if (MOVED_TO_GLOBAL.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ + " to android.provider.Settings.Global, returning read-only global URI.");
+ return Global.getUriFor(Global.CONTENT_URI, name);
+ }
return getUriFor(CONTENT_URI, name);
}
@@ -892,7 +1042,12 @@ public final class Settings {
* or not a valid integer.
*/
public static int getInt(ContentResolver cr, String name, int def) {
- String v = getString(cr, name);
+ return getIntForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
+ String v = getStringForUser(cr, name, userHandle);
try {
return v != null ? Integer.parseInt(v) : def;
} catch (NumberFormatException e) {
@@ -920,7 +1075,13 @@ public final class Settings {
*/
public static int getInt(ContentResolver cr, String name)
throws SettingNotFoundException {
- String v = getString(cr, name);
+ return getIntForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int userHandle)
+ throws SettingNotFoundException {
+ String v = getStringForUser(cr, name, userHandle);
try {
return Integer.parseInt(v);
} catch (NumberFormatException e) {
@@ -942,7 +1103,13 @@ public final class Settings {
* @return true if the value was set, false on database errors
*/
public static boolean putInt(ContentResolver cr, String name, int value) {
- return putString(cr, name, Integer.toString(value));
+ return putIntForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putIntForUser(ContentResolver cr, String name, int value,
+ int userHandle) {
+ return putStringForUser(cr, name, Integer.toString(value), userHandle);
}
/**
@@ -960,7 +1127,13 @@ public final class Settings {
* or not a valid {@code long}.
*/
public static long getLong(ContentResolver cr, String name, long def) {
- String valString = getString(cr, name);
+ return getLongForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, long def,
+ int userHandle) {
+ String valString = getStringForUser(cr, name, userHandle);
long value;
try {
value = valString != null ? Long.parseLong(valString) : def;
@@ -989,7 +1162,13 @@ public final class Settings {
*/
public static long getLong(ContentResolver cr, String name)
throws SettingNotFoundException {
- String valString = getString(cr, name);
+ return getLongForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, int userHandle)
+ throws SettingNotFoundException {
+ String valString = getStringForUser(cr, name, userHandle);
try {
return Long.parseLong(valString);
} catch (NumberFormatException e) {
@@ -1011,7 +1190,13 @@ public final class Settings {
* @return true if the value was set, false on database errors
*/
public static boolean putLong(ContentResolver cr, String name, long value) {
- return putString(cr, name, Long.toString(value));
+ return putLongForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putLongForUser(ContentResolver cr, String name, long value,
+ int userHandle) {
+ return putStringForUser(cr, name, Long.toString(value), userHandle);
}
/**
@@ -1029,7 +1214,13 @@ public final class Settings {
* or not a valid float.
*/
public static float getFloat(ContentResolver cr, String name, float def) {
- String v = getString(cr, name);
+ return getFloatForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, float def,
+ int userHandle) {
+ String v = getStringForUser(cr, name, userHandle);
try {
return v != null ? Float.parseFloat(v) : def;
} catch (NumberFormatException e) {
@@ -1057,7 +1248,13 @@ public final class Settings {
*/
public static float getFloat(ContentResolver cr, String name)
throws SettingNotFoundException {
- String v = getString(cr, name);
+ return getFloatForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, int userHandle)
+ throws SettingNotFoundException {
+ String v = getStringForUser(cr, name, userHandle);
if (v == null) {
throw new SettingNotFoundException(name);
}
@@ -1082,7 +1279,13 @@ public final class Settings {
* @return true if the value was set, false on database errors
*/
public static boolean putFloat(ContentResolver cr, String name, float value) {
- return putString(cr, name, Float.toString(value));
+ return putFloatForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putFloatForUser(ContentResolver cr, String name, float value,
+ int userHandle) {
+ return putStringForUser(cr, name, Float.toString(value), userHandle);
}
/**
@@ -1094,8 +1297,14 @@ public final class Settings {
* @param outConfig Where to place the configuration settings.
*/
public static void getConfiguration(ContentResolver cr, Configuration outConfig) {
- outConfig.fontScale = Settings.System.getFloat(
- cr, FONT_SCALE, outConfig.fontScale);
+ getConfigurationForUser(cr, outConfig, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static void getConfigurationForUser(ContentResolver cr, Configuration outConfig,
+ int userHandle) {
+ outConfig.fontScale = Settings.System.getFloatForUser(
+ cr, FONT_SCALE, outConfig.fontScale, userHandle);
if (outConfig.fontScale < 0) {
outConfig.fontScale = 1;
}
@@ -1118,7 +1327,13 @@ public final class Settings {
* @return true if the values were set, false on database errors
*/
public static boolean putConfiguration(ContentResolver cr, Configuration config) {
- return Settings.System.putFloat(cr, FONT_SCALE, config.fontScale);
+ return putConfigurationForUser(cr, config, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putConfigurationForUser(ContentResolver cr, Configuration config,
+ int userHandle) {
+ return Settings.System.putFloatForUser(cr, FONT_SCALE, config.fontScale, userHandle);
}
/** @hide */
@@ -1126,31 +1341,42 @@ public final class Settings {
return (changes&ActivityInfo.CONFIG_FONT_SCALE) != 0;
}
+ /** @deprecated - Do not use */
+ @Deprecated
public static boolean getShowGTalkServiceStatus(ContentResolver cr) {
- return getInt(cr, SHOW_GTALK_SERVICE_STATUS, 0) != 0;
+ return getShowGTalkServiceStatusForUser(cr, UserHandle.myUserId());
}
+ /**
+ * @hide
+ * @deprecated - Do not use
+ */
+ public static boolean getShowGTalkServiceStatusForUser(ContentResolver cr,
+ int userHandle) {
+ return getIntForUser(cr, SHOW_GTALK_SERVICE_STATUS, 0, userHandle) != 0;
+ }
+
+ /** @deprecated - Do not use */
+ @Deprecated
public static void setShowGTalkServiceStatus(ContentResolver cr, boolean flag) {
- putInt(cr, SHOW_GTALK_SERVICE_STATUS, flag ? 1 : 0);
+ setShowGTalkServiceStatusForUser(cr, flag, UserHandle.myUserId());
}
/**
- * The content:// style URL for this table
+ * @hide
+ * @deprecated - Do not use
*/
- public static final Uri CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/system");
+ @Deprecated
+ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean flag,
+ int userHandle) {
+ putIntForUser(cr, SHOW_GTALK_SERVICE_STATUS, flag ? 1 : 0, userHandle);
+ }
/**
- * Whether we keep the device on while the device is plugged in.
- * Supported values are:
- * <ul>
- * <li>{@code 0} to never stay on while plugged in</li>
- * <li>{@link BatteryManager#BATTERY_PLUGGED_AC} to stay on for AC charger</li>
- * <li>{@link BatteryManager#BATTERY_PLUGGED_USB} to stay on for USB charger</li>
- * </ul>
- * These values can be OR-ed together.
+ * @deprecated Use {@link android.provider.Settings.Global#STAY_ON_WHILE_PLUGGED_IN} instead
*/
- public static final String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
+ @Deprecated
+ public static final String STAY_ON_WHILE_PLUGGED_IN = Global.STAY_ON_WHILE_PLUGGED_IN;
/**
* What happens when the user presses the end call button if they're not
@@ -1195,122 +1421,146 @@ public final class Settings {
public static final int ADVANCED_SETTINGS_DEFAULT = 0;
/**
- * Whether Airplane Mode is on.
+ * @deprecated Use {@link android.provider.Settings.Global#AIRPLANE_MODE_ON} instead
*/
- public static final String AIRPLANE_MODE_ON = "airplane_mode_on";
+ @Deprecated
+ public static final String AIRPLANE_MODE_ON = Global.AIRPLANE_MODE_ON;
/**
- * Constant for use in AIRPLANE_MODE_RADIOS to specify Bluetooth radio.
+ * @deprecated Use {@link android.provider.Settings.Global#RADIO_BLUETOOTH} instead
*/
- public static final String RADIO_BLUETOOTH = "bluetooth";
+ @Deprecated
+ public static final String RADIO_BLUETOOTH = Global.RADIO_BLUETOOTH;
/**
- * Constant for use in AIRPLANE_MODE_RADIOS to specify Wi-Fi radio.
+ * @deprecated Use {@link android.provider.Settings.Global#RADIO_WIFI} instead
*/
- public static final String RADIO_WIFI = "wifi";
+ @Deprecated
+ public static final String RADIO_WIFI = Global.RADIO_WIFI;
/**
+ * @deprecated Use {@link android.provider.Settings.Global#RADIO_WIMAX} instead
* {@hide}
*/
- public static final String RADIO_WIMAX = "wimax";
+ @Deprecated
+ public static final String RADIO_WIMAX = Global.RADIO_WIMAX;
+
/**
- * Constant for use in AIRPLANE_MODE_RADIOS to specify Cellular radio.
+ * @deprecated Use {@link android.provider.Settings.Global#RADIO_CELL} instead
*/
- public static final String RADIO_CELL = "cell";
+ @Deprecated
+ public static final String RADIO_CELL = Global.RADIO_CELL;
/**
- * Constant for use in AIRPLANE_MODE_RADIOS to specify NFC radio.
+ * @deprecated Use {@link android.provider.Settings.Global#RADIO_NFC} instead
*/
- public static final String RADIO_NFC = "nfc";
+ @Deprecated
+ public static final String RADIO_NFC = Global.RADIO_NFC;
/**
- * A comma separated list of radios that need to be disabled when airplane mode
- * is on. This overrides WIFI_ON and BLUETOOTH_ON, if Wi-Fi and bluetooth are
- * included in the comma separated list.
+ * @deprecated Use {@link android.provider.Settings.Global#AIRPLANE_MODE_RADIOS} instead
*/
- public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios";
+ @Deprecated
+ public static final String AIRPLANE_MODE_RADIOS = Global.AIRPLANE_MODE_RADIOS;
/**
- * A comma separated list of radios that should to be disabled when airplane mode
- * is on, but can be manually reenabled by the user. For example, if RADIO_WIFI is
- * added to both AIRPLANE_MODE_RADIOS and AIRPLANE_MODE_TOGGLEABLE_RADIOS, then Wifi
- * will be turned off when entering airplane mode, but the user will be able to reenable
- * Wifi in the Settings app.
+ * @deprecated Use {@link android.provider.Settings.Global#AIRPLANE_MODE_TOGGLEABLE_RADIOS} instead
*
* {@hide}
*/
- public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
+ @Deprecated
+ public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS =
+ Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS;
/**
- * The policy for deciding when Wi-Fi should go to sleep (which will in
- * turn switch to using the mobile data as an Internet connection).
- * <p>
- * Set to one of {@link #WIFI_SLEEP_POLICY_DEFAULT},
- * {@link #WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED}, or
- * {@link #WIFI_SLEEP_POLICY_NEVER}.
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_SLEEP_POLICY} instead
*/
- public static final String WIFI_SLEEP_POLICY = "wifi_sleep_policy";
+ @Deprecated
+ public static final String WIFI_SLEEP_POLICY = Global.WIFI_SLEEP_POLICY;
/**
- * Value for {@link #WIFI_SLEEP_POLICY} to use the default Wi-Fi sleep
- * policy, which is to sleep shortly after the turning off
- * according to the {@link #STAY_ON_WHILE_PLUGGED_IN} setting.
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_SLEEP_POLICY_DEFAULT} instead
*/
- public static final int WIFI_SLEEP_POLICY_DEFAULT = 0;
+ @Deprecated
+ public static final int WIFI_SLEEP_POLICY_DEFAULT = Global.WIFI_SLEEP_POLICY_DEFAULT;
/**
- * Value for {@link #WIFI_SLEEP_POLICY} to use the default policy when
- * the device is on battery, and never go to sleep when the device is
- * plugged in.
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED} instead
*/
- public static final int WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED = 1;
+ @Deprecated
+ public static final int WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED =
+ Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED;
/**
- * Value for {@link #WIFI_SLEEP_POLICY} to never go to sleep.
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_SLEEP_POLICY_NEVER} instead
*/
- public static final int WIFI_SLEEP_POLICY_NEVER = 2;
+ @Deprecated
+ public static final int WIFI_SLEEP_POLICY_NEVER = Global.WIFI_SLEEP_POLICY_NEVER;
+
+ /**
+ * @deprecated Use {@link android.provider.Settings.Global#MODE_RINGER} instead
+ */
+ @Deprecated
+ public static final String MODE_RINGER = Global.MODE_RINGER;
- //TODO: deprecate static IP constants
/**
* Whether to use static IP and other static network attributes.
* <p>
* Set to 1 for true and 0 for false.
+ *
+ * @deprecated Use {@link WifiManager} instead
*/
+ @Deprecated
public static final String WIFI_USE_STATIC_IP = "wifi_use_static_ip";
/**
* The static IP address.
* <p>
* Example: "192.168.1.51"
+ *
+ * @deprecated Use {@link WifiManager} instead
*/
+ @Deprecated
public static final String WIFI_STATIC_IP = "wifi_static_ip";
/**
* If using static IP, the gateway's IP address.
* <p>
* Example: "192.168.1.1"
+ *
+ * @deprecated Use {@link WifiManager} instead
*/
+ @Deprecated
public static final String WIFI_STATIC_GATEWAY = "wifi_static_gateway";
/**
* If using static IP, the net mask.
* <p>
* Example: "255.255.255.0"
+ *
+ * @deprecated Use {@link WifiManager} instead
*/
+ @Deprecated
public static final String WIFI_STATIC_NETMASK = "wifi_static_netmask";
/**
* If using static IP, the primary DNS's IP address.
* <p>
* Example: "192.168.1.1"
+ *
+ * @deprecated Use {@link WifiManager} instead
*/
+ @Deprecated
public static final String WIFI_STATIC_DNS1 = "wifi_static_dns1";
/**
* If using static IP, the secondary DNS's IP address.
* <p>
* Example: "192.168.1.2"
+ *
+ * @deprecated Use {@link WifiManager} instead
*/
+ @Deprecated
public static final String WIFI_STATIC_DNS2 = "wifi_static_dns2";
@@ -1370,18 +1620,26 @@ public final class Settings {
/**
* Name of an application package to be debugged.
+ *
+ * @deprecated Use {@link Global#DEBUG_APP} instead
*/
- public static final String DEBUG_APP = "debug_app";
+ @Deprecated
+ public static final String DEBUG_APP = Global.DEBUG_APP;
/**
* If 1, when launching DEBUG_APP it will wait for the debugger before
* starting user code. If 0, it will run normally.
+ *
+ * @deprecated Use {@link Global#WAIT_FOR_DEBUGGER} instead
*/
- public static final String WAIT_FOR_DEBUGGER = "wait_for_debugger";
+ @Deprecated
+ public static final String WAIT_FOR_DEBUGGER = Global.WAIT_FOR_DEBUGGER;
/**
* Whether or not to dim the screen. 0=no 1=yes
+ * @deprecated This setting is no longer used.
*/
+ @Deprecated
public static final String DIM_SCREEN = "dim_screen";
/**
@@ -1390,14 +1648,6 @@ public final class Settings {
public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
/**
- * If 0, the compatibility mode is off for all applications.
- * If 1, older applications run under compatibility mode.
- * TODO: remove this settings before code freeze (bug/1907571)
- * @hide
- */
- public static final String COMPATIBILITY_MODE = "compatibility_mode";
-
- /**
* The screen backlight brightness between 0 and 255.
*/
public static final String SCREEN_BRIGHTNESS = "screen_brightness";
@@ -1426,23 +1676,21 @@ public final class Settings {
/**
* Control whether the process CPU usage meter should be shown.
+ *
+ * @deprecated Use {@link Global#SHOW_PROCESSES} instead
*/
- public static final String SHOW_PROCESSES = "show_processes";
+ @Deprecated
+ public static final String SHOW_PROCESSES = Global.SHOW_PROCESSES;
/**
* If 1, the activity manager will aggressively finish activities and
* processes as soon as they are no longer needed. If 0, the normal
* extended lifetime is used.
+ *
+ * @deprecated Use {@link Global#ALWAYS_FINISH_ACTIVITIES} instead
*/
- public static final String ALWAYS_FINISH_ACTIVITIES =
- "always_finish_activities";
-
-
- /**
- * Ringer mode. This is used internally, changing this value will not
- * change the ringer mode. See AudioManager.
- */
- public static final String MODE_RINGER = "mode_ringer";
+ @Deprecated
+ public static final String ALWAYS_FINISH_ACTIVITIES = Global.ALWAYS_FINISH_ACTIVITIES;
/**
* Determines which streams are affected by ringer mode changes. The
@@ -1666,20 +1914,25 @@ public final class Settings {
/**
* Name of activity to use for wallpaper on the home screen.
+ *
+ * @deprecated Use {@link WallpaperManager} instead.
*/
+ @Deprecated
public static final String WALLPAPER_ACTIVITY = "wallpaper_activity";
/**
- * Value to specify if the user prefers the date, time and time zone
- * to be automatically fetched from the network (NITZ). 1=yes, 0=no
+ * @deprecated Use {@link android.provider.Settings.Global#AUTO_TIME}
+ * instead
*/
- public static final String AUTO_TIME = "auto_time";
+ @Deprecated
+ public static final String AUTO_TIME = Global.AUTO_TIME;
/**
- * Value to specify if the user prefers the time zone
- * to be automatically fetched from the network (NITZ). 1=yes, 0=no
+ * @deprecated Use {@link android.provider.Settings.Global#AUTO_TIME_ZONE}
+ * instead
*/
- public static final String AUTO_TIME_ZONE = "auto_time_zone";
+ @Deprecated
+ public static final String AUTO_TIME_ZONE = Global.AUTO_TIME_ZONE;
/**
* Display times as 12 or 24 hours
@@ -1708,28 +1961,30 @@ public final class Settings {
/**
* Scaling factor for normal window animations. Setting to 0 will disable window
* animations.
+ *
+ * @deprecated Use {@link Global#WINDOW_ANIMATION_SCALE} instead
*/
- public static final String WINDOW_ANIMATION_SCALE = "window_animation_scale";
+ @Deprecated
+ public static final String WINDOW_ANIMATION_SCALE = Global.WINDOW_ANIMATION_SCALE;
/**
* Scaling factor for activity transition animations. Setting to 0 will disable window
* animations.
+ *
+ * @deprecated Use {@link Global#TRANSITION_ANIMATION_SCALE} instead
*/
- public static final String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
+ @Deprecated
+ public static final String TRANSITION_ANIMATION_SCALE = Global.TRANSITION_ANIMATION_SCALE;
/**
* Scaling factor for Animator-based animations. This affects both the start delay and
* duration of all such animations. Setting to 0 will cause animations to end immediately.
* The default value is 1.
+ *
+ * @deprecated Use {@link Global#ANIMATOR_DURATION_SCALE} instead
*/
- public static final String ANIMATOR_DURATION_SCALE = "animator_duration_scale";
-
- /**
- * Scaling factor for normal window animations. Setting to 0 will disable window
- * animations.
- * @hide
- */
- public static final String FANCY_IME_ANIMATIONS = "fancy_ime_animations";
+ @Deprecated
+ public static final String ANIMATOR_DURATION_SCALE = Global.ANIMATOR_DURATION_SCALE;
/**
* Control whether the accelerometer will be used to change screen
@@ -1793,23 +2048,6 @@ public final class Settings {
public static final String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type";
/**
- * CDMA only settings
- * Emergency Tone 0 = Off
- * 1 = Alert
- * 2 = Vibrate
- * @hide
- */
- public static final String EMERGENCY_TONE = "emergency_tone";
-
- /**
- * CDMA only settings
- * Whether the auto retry is enabled. The value is
- * boolean (1 or 0).
- * @hide
- */
- public static final String CALL_AUTO_RETRY = "call_auto_retry";
-
- /**
* Whether the hearing aid is enabled. The value is
* boolean (1 or 0).
* @hide
@@ -1880,16 +2118,20 @@ public final class Settings {
"window_orientation_listener_log";
/**
- * Whether to play a sound for low-battery alerts.
+ * @deprecated Use {@link android.provider.Settings.Global#POWER_SOUNDS_ENABLED}
+ * instead
* @hide
*/
- public static final String POWER_SOUNDS_ENABLED = "power_sounds_enabled";
+ @Deprecated
+ public static final String POWER_SOUNDS_ENABLED = Global.POWER_SOUNDS_ENABLED;
/**
- * Whether to play a sound for dock events.
+ * @deprecated Use {@link android.provider.Settings.Global#DOCK_SOUNDS_ENABLED}
+ * instead
* @hide
*/
- public static final String DOCK_SOUNDS_ENABLED = "dock_sounds_enabled";
+ @Deprecated
+ public static final String DOCK_SOUNDS_ENABLED = Global.DOCK_SOUNDS_ENABLED;
/**
* Whether to play sounds when the keyguard is shown and dismissed.
@@ -1904,46 +2146,60 @@ public final class Settings {
public static final String LOCKSCREEN_DISABLED = "lockscreen.disabled";
/**
- * URI for the low battery sound file.
+ * @deprecated Use {@link android.provider.Settings.Global#LOW_BATTERY_SOUND}
+ * instead
* @hide
*/
- public static final String LOW_BATTERY_SOUND = "low_battery_sound";
+ @Deprecated
+ public static final String LOW_BATTERY_SOUND = Global.LOW_BATTERY_SOUND;
/**
- * URI for the desk dock "in" event sound.
+ * @deprecated Use {@link android.provider.Settings.Global#DESK_DOCK_SOUND}
+ * instead
* @hide
*/
- public static final String DESK_DOCK_SOUND = "desk_dock_sound";
+ @Deprecated
+ public static final String DESK_DOCK_SOUND = Global.DESK_DOCK_SOUND;
/**
- * URI for the desk dock "out" event sound.
+ * @deprecated Use {@link android.provider.Settings.Global#DESK_UNDOCK_SOUND}
+ * instead
* @hide
*/
- public static final String DESK_UNDOCK_SOUND = "desk_undock_sound";
+ @Deprecated
+ public static final String DESK_UNDOCK_SOUND = Global.DESK_UNDOCK_SOUND;
/**
- * URI for the car dock "in" event sound.
+ * @deprecated Use {@link android.provider.Settings.Global#CAR_DOCK_SOUND}
+ * instead
* @hide
*/
- public static final String CAR_DOCK_SOUND = "car_dock_sound";
+ @Deprecated
+ public static final String CAR_DOCK_SOUND = Global.CAR_DOCK_SOUND;
/**
- * URI for the car dock "out" event sound.
+ * @deprecated Use {@link android.provider.Settings.Global#CAR_UNDOCK_SOUND}
+ * instead
* @hide
*/
- public static final String CAR_UNDOCK_SOUND = "car_undock_sound";
+ @Deprecated
+ public static final String CAR_UNDOCK_SOUND = Global.CAR_UNDOCK_SOUND;
/**
- * URI for the "device locked" (keyguard shown) sound.
+ * @deprecated Use {@link android.provider.Settings.Global#LOCK_SOUND}
+ * instead
* @hide
*/
- public static final String LOCK_SOUND = "lock_sound";
+ @Deprecated
+ public static final String LOCK_SOUND = Global.LOCK_SOUND;
/**
- * URI for the "device unlocked" (keyguard dismissed) sound.
+ * @deprecated Use {@link android.provider.Settings.Global#UNLOCK_SOUND}
+ * instead
* @hide
*/
- public static final String UNLOCK_SOUND = "unlock_sound";
+ @Deprecated
+ public static final String UNLOCK_SOUND = Global.UNLOCK_SOUND;
/**
* Receive incoming SIP calls?
@@ -2043,8 +2299,8 @@ public final class Settings {
DATE_FORMAT,
DTMF_TONE_WHEN_DIALING,
DTMF_TONE_TYPE_WHEN_DIALING,
- EMERGENCY_TONE,
- CALL_AUTO_RETRY,
+ Global.EMERGENCY_TONE,
+ Global.CALL_AUTO_RETRY,
HEARING_AID,
TTY_MODE,
SOUND_EFFECTS_ENABLED,
@@ -2063,11 +2319,11 @@ public final class Settings {
// Settings moved to Settings.Secure
/**
- * @deprecated Use {@link android.provider.Settings.Secure#ADB_ENABLED}
+ * @deprecated Use {@link android.provider.Settings.Global#ADB_ENABLED}
* instead
*/
@Deprecated
- public static final String ADB_ENABLED = Secure.ADB_ENABLED;
+ public static final String ADB_ENABLED = Global.ADB_ENABLED;
/**
* @deprecated Use {@link android.provider.Settings.Secure#ANDROID_ID} instead
@@ -2076,34 +2332,34 @@ public final class Settings {
public static final String ANDROID_ID = Secure.ANDROID_ID;
/**
- * @deprecated Use {@link android.provider.Settings.Secure#BLUETOOTH_ON} instead
+ * @deprecated Use {@link android.provider.Settings.Global#BLUETOOTH_ON} instead
*/
@Deprecated
- public static final String BLUETOOTH_ON = Secure.BLUETOOTH_ON;
+ public static final String BLUETOOTH_ON = Global.BLUETOOTH_ON;
/**
- * @deprecated Use {@link android.provider.Settings.Secure#DATA_ROAMING} instead
+ * @deprecated Use {@link android.provider.Settings.Global#DATA_ROAMING} instead
*/
@Deprecated
- public static final String DATA_ROAMING = Secure.DATA_ROAMING;
+ public static final String DATA_ROAMING = Global.DATA_ROAMING;
/**
- * @deprecated Use {@link android.provider.Settings.Secure#DEVICE_PROVISIONED} instead
+ * @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead
*/
@Deprecated
- public static final String DEVICE_PROVISIONED = Secure.DEVICE_PROVISIONED;
+ public static final String DEVICE_PROVISIONED = Global.DEVICE_PROVISIONED;
/**
- * @deprecated Use {@link android.provider.Settings.Secure#HTTP_PROXY} instead
+ * @deprecated Use {@link android.provider.Settings.Global#HTTP_PROXY} instead
*/
@Deprecated
- public static final String HTTP_PROXY = Secure.HTTP_PROXY;
+ public static final String HTTP_PROXY = Global.HTTP_PROXY;
/**
- * @deprecated Use {@link android.provider.Settings.Secure#INSTALL_NON_MARKET_APPS} instead
+ * @deprecated Use {@link android.provider.Settings.Global#INSTALL_NON_MARKET_APPS} instead
*/
@Deprecated
- public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS;
+ public static final String INSTALL_NON_MARKET_APPS = Global.INSTALL_NON_MARKET_APPS;
/**
* @deprecated Use {@link android.provider.Settings.Secure#LOCATION_PROVIDERS_ALLOWED}
@@ -2119,10 +2375,10 @@ public final class Settings {
public static final String LOGGING_ID = Secure.LOGGING_ID;
/**
- * @deprecated Use {@link android.provider.Settings.Secure#NETWORK_PREFERENCE} instead
+ * @deprecated Use {@link android.provider.Settings.Global#NETWORK_PREFERENCE} instead
*/
@Deprecated
- public static final String NETWORK_PREFERENCE = Secure.NETWORK_PREFERENCE;
+ public static final String NETWORK_PREFERENCE = Global.NETWORK_PREFERENCE;
/**
* @deprecated Use {@link android.provider.Settings.Secure#PARENTAL_CONTROL_ENABLED}
@@ -2153,60 +2409,60 @@ public final class Settings {
public static final String SETTINGS_CLASSNAME = Secure.SETTINGS_CLASSNAME;
/**
- * @deprecated Use {@link android.provider.Settings.Secure#USB_MASS_STORAGE_ENABLED} instead
+ * @deprecated Use {@link android.provider.Settings.Global#USB_MASS_STORAGE_ENABLED} instead
*/
@Deprecated
- public static final String USB_MASS_STORAGE_ENABLED = Secure.USB_MASS_STORAGE_ENABLED;
+ public static final String USB_MASS_STORAGE_ENABLED = Global.USB_MASS_STORAGE_ENABLED;
/**
- * @deprecated Use {@link android.provider.Settings.Secure#USE_GOOGLE_MAIL} instead
+ * @deprecated Use {@link android.provider.Settings.Global#USE_GOOGLE_MAIL} instead
*/
@Deprecated
- public static final String USE_GOOGLE_MAIL = Secure.USE_GOOGLE_MAIL;
+ public static final String USE_GOOGLE_MAIL = Global.USE_GOOGLE_MAIL;
/**
* @deprecated Use
- * {@link android.provider.Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT} instead
+ * {@link android.provider.Settings.Global#WIFI_MAX_DHCP_RETRY_COUNT} instead
*/
@Deprecated
- public static final String WIFI_MAX_DHCP_RETRY_COUNT = Secure.WIFI_MAX_DHCP_RETRY_COUNT;
+ public static final String WIFI_MAX_DHCP_RETRY_COUNT = Global.WIFI_MAX_DHCP_RETRY_COUNT;
/**
* @deprecated Use
- * {@link android.provider.Settings.Secure#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS} instead
+ * {@link android.provider.Settings.Global#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS} instead
*/
@Deprecated
public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS =
- Secure.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS;
+ Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS;
/**
* @deprecated Use
- * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON} instead
+ * {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON} instead
*/
@Deprecated
public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
- Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON;
+ Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON;
/**
* @deprecated Use
- * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} instead
+ * {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} instead
*/
@Deprecated
public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
- Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY;
+ Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY;
/**
- * @deprecated Use {@link android.provider.Settings.Secure#WIFI_NUM_OPEN_NETWORKS_KEPT}
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_NUM_OPEN_NETWORKS_KEPT}
* instead
*/
@Deprecated
- public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = Secure.WIFI_NUM_OPEN_NETWORKS_KEPT;
+ public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = Global.WIFI_NUM_OPEN_NETWORKS_KEPT;
/**
- * @deprecated Use {@link android.provider.Settings.Secure#WIFI_ON} instead
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_ON} instead
*/
@Deprecated
- public static final String WIFI_ON = Secure.WIFI_ON;
+ public static final String WIFI_ON = Global.WIFI_ON;
/**
* @deprecated Use
@@ -2264,10 +2520,10 @@ public final class Settings {
public static final String WIFI_WATCHDOG_MAX_AP_CHECKS = Secure.WIFI_WATCHDOG_MAX_AP_CHECKS;
/**
- * @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_ON} instead
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_WATCHDOG_ON} instead
*/
@Deprecated
- public static final String WIFI_WATCHDOG_ON = Secure.WIFI_WATCHDOG_ON;
+ public static final String WIFI_WATCHDOG_ON = Global.WIFI_WATCHDOG_ON;
/**
* @deprecated Use {@link android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT} instead
@@ -2300,18 +2556,150 @@ public final class Settings {
public static final class Secure extends NameValueTable {
public static final String SYS_PROP_SETTING_VERSION = "sys.settings_secure_version";
+ /**
+ * The content:// style URL for this table
+ */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://" + AUTHORITY + "/secure");
+
// Populated lazily, guarded by class object:
- private static NameValueCache sNameValueCache = null;
+ private static final NameValueCache sNameValueCache = new NameValueCache(
+ SYS_PROP_SETTING_VERSION,
+ CONTENT_URI,
+ CALL_METHOD_GET_SECURE,
+ CALL_METHOD_PUT_SECURE);
private static ILockSettings sLockSettings = null;
private static boolean sIsSystemProcess;
private static final HashSet<String> MOVED_TO_LOCK_SETTINGS;
+ private static final HashSet<String> MOVED_TO_GLOBAL;
static {
MOVED_TO_LOCK_SETTINGS = new HashSet<String>(3);
MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_ENABLED);
MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_VISIBLE);
MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED);
+
+ MOVED_TO_GLOBAL = new HashSet<String>();
+ MOVED_TO_GLOBAL.add(Settings.Global.ADB_ENABLED);
+ MOVED_TO_GLOBAL.add(Settings.Global.ASSISTED_GPS_ENABLED);
+ MOVED_TO_GLOBAL.add(Settings.Global.BLUETOOTH_ON);
+ MOVED_TO_GLOBAL.add(Settings.Global.CDMA_CELL_BROADCAST_SMS);
+ MOVED_TO_GLOBAL.add(Settings.Global.CDMA_ROAMING_MODE);
+ MOVED_TO_GLOBAL.add(Settings.Global.CDMA_SUBSCRIPTION_MODE);
+ MOVED_TO_GLOBAL.add(Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE);
+ MOVED_TO_GLOBAL.add(Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI);
+ MOVED_TO_GLOBAL.add(Settings.Global.DATA_ROAMING);
+ MOVED_TO_GLOBAL.add(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED);
+ MOVED_TO_GLOBAL.add(Settings.Global.DEVICE_PROVISIONED);
+ MOVED_TO_GLOBAL.add(Settings.Global.DISPLAY_DENSITY_FORCED);
+ MOVED_TO_GLOBAL.add(Settings.Global.DISPLAY_SIZE_FORCED);
+ MOVED_TO_GLOBAL.add(Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
+ MOVED_TO_GLOBAL.add(Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
+ MOVED_TO_GLOBAL.add(Settings.Global.INSTALL_NON_MARKET_APPS);
+ MOVED_TO_GLOBAL.add(Settings.Global.MOBILE_DATA);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_DEV_BUCKET_DURATION);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_DEV_DELETE_AGE);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_DEV_PERSIST_BYTES);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_DEV_ROTATE_AGE);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_ENABLED);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_POLL_INTERVAL);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_REPORT_XT_OVER_DEV);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_SAMPLE_ENABLED);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_BUCKET_DURATION);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_DELETE_AGE);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_PERSIST_BYTES);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_ROTATE_AGE);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_TAG_DELETE_AGE);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE);
+ MOVED_TO_GLOBAL.add(Settings.Global.NETWORK_PREFERENCE);
+ MOVED_TO_GLOBAL.add(Settings.Global.NITZ_UPDATE_DIFF);
+ MOVED_TO_GLOBAL.add(Settings.Global.NITZ_UPDATE_SPACING);
+ MOVED_TO_GLOBAL.add(Settings.Global.NTP_SERVER);
+ MOVED_TO_GLOBAL.add(Settings.Global.NTP_TIMEOUT);
+ MOVED_TO_GLOBAL.add(Settings.Global.PDP_WATCHDOG_ERROR_POLL_COUNT);
+ MOVED_TO_GLOBAL.add(Settings.Global.PDP_WATCHDOG_LONG_POLL_INTERVAL_MS);
+ MOVED_TO_GLOBAL.add(Settings.Global.PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT);
+ MOVED_TO_GLOBAL.add(Settings.Global.PDP_WATCHDOG_POLL_INTERVAL_MS);
+ MOVED_TO_GLOBAL.add(Settings.Global.PDP_WATCHDOG_TRIGGER_PACKET_COUNT);
+ MOVED_TO_GLOBAL.add(Settings.Global.SAMPLING_PROFILER_MS);
+ MOVED_TO_GLOBAL.add(Settings.Global.SETUP_PREPAID_DATA_SERVICE_URL);
+ MOVED_TO_GLOBAL.add(Settings.Global.SETUP_PREPAID_DETECTION_REDIR_HOST);
+ MOVED_TO_GLOBAL.add(Settings.Global.SETUP_PREPAID_DETECTION_TARGET_URL);
+ MOVED_TO_GLOBAL.add(Settings.Global.TETHER_DUN_APN);
+ MOVED_TO_GLOBAL.add(Settings.Global.TETHER_DUN_REQUIRED);
+ MOVED_TO_GLOBAL.add(Settings.Global.TETHER_SUPPORTED);
+ MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_HELP_URI);
+ MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_MAX_NTP_CACHE_AGE_SEC);
+ MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_NOTIFICATION_TYPE);
+ MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_POLLING_SEC);
+ MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_RESET_DAY);
+ MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_THRESHOLD_BYTES);
+ MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_VALUE_KBITSPS);
+ MOVED_TO_GLOBAL.add(Settings.Global.USB_MASS_STORAGE_ENABLED);
+ MOVED_TO_GLOBAL.add(Settings.Global.USE_GOOGLE_MAIL);
+ MOVED_TO_GLOBAL.add(Settings.Global.WEB_AUTOFILL_QUERY_URL);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_COUNTRY_CODE);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_FREQUENCY_BAND);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_IDLE_MS);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NUM_OPEN_NETWORKS_KEPT);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_ON);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_P2P_DEVICE_NAME);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SAVED_STATE);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_WATCHDOG_ON);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED);
+ MOVED_TO_GLOBAL.add(Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON);
+ MOVED_TO_GLOBAL.add(Settings.Global.PACKAGE_VERIFIER_ENABLE);
+ MOVED_TO_GLOBAL.add(Settings.Global.PACKAGE_VERIFIER_TIMEOUT);
+ MOVED_TO_GLOBAL.add(Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE);
+ MOVED_TO_GLOBAL.add(Settings.Global.DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS);
+ MOVED_TO_GLOBAL.add(Settings.Global.DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS);
+ MOVED_TO_GLOBAL.add(Settings.Global.GPRS_REGISTER_CHECK_PERIOD_MS);
+ MOVED_TO_GLOBAL.add(Settings.Global.WTF_IS_FATAL);
+ MOVED_TO_GLOBAL.add(Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD);
+ MOVED_TO_GLOBAL.add(Settings.Global.BATTERY_DISCHARGE_THRESHOLD);
+ MOVED_TO_GLOBAL.add(Settings.Global.SEND_ACTION_APP_ERROR);
+ MOVED_TO_GLOBAL.add(Settings.Global.DROPBOX_AGE_SECONDS);
+ MOVED_TO_GLOBAL.add(Settings.Global.DROPBOX_MAX_FILES);
+ MOVED_TO_GLOBAL.add(Settings.Global.DROPBOX_QUOTA_KB);
+ MOVED_TO_GLOBAL.add(Settings.Global.DROPBOX_QUOTA_PERCENT);
+ MOVED_TO_GLOBAL.add(Settings.Global.DROPBOX_RESERVE_PERCENT);
+ MOVED_TO_GLOBAL.add(Settings.Global.DROPBOX_TAG_PREFIX);
+ MOVED_TO_GLOBAL.add(Settings.Global.ERROR_LOGCAT_PREFIX);
+ MOVED_TO_GLOBAL.add(Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL);
+ MOVED_TO_GLOBAL.add(Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD);
+ MOVED_TO_GLOBAL.add(Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE);
+ MOVED_TO_GLOBAL.add(Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES);
+ MOVED_TO_GLOBAL.add(Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES);
+ MOVED_TO_GLOBAL.add(Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS);
+ MOVED_TO_GLOBAL.add(Settings.Global.CONNECTIVITY_CHANGE_DELAY);
+ MOVED_TO_GLOBAL.add(Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED);
+ MOVED_TO_GLOBAL.add(Settings.Global.CAPTIVE_PORTAL_SERVER);
+ MOVED_TO_GLOBAL.add(Settings.Global.NSD_ON);
+ MOVED_TO_GLOBAL.add(Settings.Global.SET_INSTALL_LOCATION);
+ MOVED_TO_GLOBAL.add(Settings.Global.DEFAULT_INSTALL_LOCATION);
+ MOVED_TO_GLOBAL.add(Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY);
+ MOVED_TO_GLOBAL.add(Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY);
+ MOVED_TO_GLOBAL.add(Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT);
+ MOVED_TO_GLOBAL.add(Settings.Global.HTTP_PROXY);
+ MOVED_TO_GLOBAL.add(Settings.Global.GLOBAL_HTTP_PROXY_HOST);
+ MOVED_TO_GLOBAL.add(Settings.Global.GLOBAL_HTTP_PROXY_PORT);
+ MOVED_TO_GLOBAL.add(Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
+ MOVED_TO_GLOBAL.add(Settings.Global.SET_GLOBAL_HTTP_PROXY);
+ MOVED_TO_GLOBAL.add(Settings.Global.DEFAULT_DNS_SERVER);
+ MOVED_TO_GLOBAL.add(Settings.Global.PREFERRED_NETWORK_MODE);
+ MOVED_TO_GLOBAL.add(Settings.Global.PREFERRED_CDMA_SUBSCRIPTION);
}
/**
@@ -2320,27 +2708,37 @@ public final class Settings {
* @param name to look up in the table
* @return the corresponding value, or null if not present
*/
- public synchronized static String getString(ContentResolver resolver, String name) {
- if (sNameValueCache == null) {
- sNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI,
- CALL_METHOD_GET_SECURE);
- }
+ public static String getString(ContentResolver resolver, String name) {
+ return getStringForUser(resolver, name, UserHandle.myUserId());
+ }
- if (sLockSettings == null) {
- sLockSettings = ILockSettings.Stub.asInterface(
- (IBinder) ServiceManager.getService("lock_settings"));
- sIsSystemProcess = Process.myUid() == Process.SYSTEM_UID;
+ /** @hide */
+ public static String getStringForUser(ContentResolver resolver, String name,
+ int userHandle) {
+ if (MOVED_TO_GLOBAL.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"
+ + " to android.provider.Settings.Global.");
+ return Global.getStringForUser(resolver, name, userHandle);
}
- if (sLockSettings != null && !sIsSystemProcess
- && MOVED_TO_LOCK_SETTINGS.contains(name)) {
- try {
- return sLockSettings.getString(name, "0", UserId.getCallingUserId());
- } catch (RemoteException re) {
- // Fall through
+
+ if (MOVED_TO_LOCK_SETTINGS.contains(name)) {
+ synchronized (Secure.class) {
+ if (sLockSettings == null) {
+ sLockSettings = ILockSettings.Stub.asInterface(
+ (IBinder) ServiceManager.getService("lock_settings"));
+ sIsSystemProcess = Process.myUid() == Process.SYSTEM_UID;
+ }
+ }
+ if (sLockSettings != null && !sIsSystemProcess) {
+ try {
+ return sLockSettings.getString(name, "0", userHandle);
+ } catch (RemoteException re) {
+ // Fall through
+ }
}
}
- return sNameValueCache.getString(resolver, name);
+ return sNameValueCache.getStringForUser(resolver, name, userHandle);
}
/**
@@ -2350,9 +2748,19 @@ public final class Settings {
* @param value to associate with the name
* @return true if the value was set, false on database errors
*/
- public static boolean putString(ContentResolver resolver,
- String name, String value) {
- return putString(resolver, CONTENT_URI, name, value);
+ public static boolean putString(ContentResolver resolver, String name, String value) {
+ return putStringForUser(resolver, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putStringForUser(ContentResolver resolver, String name, String value,
+ int userHandle) {
+ if (MOVED_TO_GLOBAL.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ + " to android.provider.Settings.Global");
+ return Global.putStringForUser(resolver, name, value, userHandle);
+ }
+ return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
}
/**
@@ -2362,6 +2770,11 @@ public final class Settings {
* @return the corresponding content URI, or null if not present
*/
public static Uri getUriFor(String name) {
+ if (MOVED_TO_GLOBAL.contains(name)) {
+ Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"
+ + " to android.provider.Settings.Global, returning global URI.");
+ return Global.getUriFor(Global.CONTENT_URI, name);
+ }
return getUriFor(CONTENT_URI, name);
}
@@ -2380,7 +2793,12 @@ public final class Settings {
* or not a valid integer.
*/
public static int getInt(ContentResolver cr, String name, int def) {
- String v = getString(cr, name);
+ return getIntForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
+ String v = getStringForUser(cr, name, userHandle);
try {
return v != null ? Integer.parseInt(v) : def;
} catch (NumberFormatException e) {
@@ -2408,7 +2826,13 @@ public final class Settings {
*/
public static int getInt(ContentResolver cr, String name)
throws SettingNotFoundException {
- String v = getString(cr, name);
+ return getIntForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static int getIntForUser(ContentResolver cr, String name, int userHandle)
+ throws SettingNotFoundException {
+ String v = getStringForUser(cr, name, userHandle);
try {
return Integer.parseInt(v);
} catch (NumberFormatException e) {
@@ -2430,7 +2854,13 @@ public final class Settings {
* @return true if the value was set, false on database errors
*/
public static boolean putInt(ContentResolver cr, String name, int value) {
- return putString(cr, name, Integer.toString(value));
+ return putIntForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putIntForUser(ContentResolver cr, String name, int value,
+ int userHandle) {
+ return putStringForUser(cr, name, Integer.toString(value), userHandle);
}
/**
@@ -2448,7 +2878,13 @@ public final class Settings {
* or not a valid {@code long}.
*/
public static long getLong(ContentResolver cr, String name, long def) {
- String valString = getString(cr, name);
+ return getLongForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, long def,
+ int userHandle) {
+ String valString = getStringForUser(cr, name, userHandle);
long value;
try {
value = valString != null ? Long.parseLong(valString) : def;
@@ -2477,7 +2913,13 @@ public final class Settings {
*/
public static long getLong(ContentResolver cr, String name)
throws SettingNotFoundException {
- String valString = getString(cr, name);
+ return getLongForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static long getLongForUser(ContentResolver cr, String name, int userHandle)
+ throws SettingNotFoundException {
+ String valString = getStringForUser(cr, name, userHandle);
try {
return Long.parseLong(valString);
} catch (NumberFormatException e) {
@@ -2499,7 +2941,13 @@ public final class Settings {
* @return true if the value was set, false on database errors
*/
public static boolean putLong(ContentResolver cr, String name, long value) {
- return putString(cr, name, Long.toString(value));
+ return putLongForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putLongForUser(ContentResolver cr, String name, long value,
+ int userHandle) {
+ return putStringForUser(cr, name, Long.toString(value), userHandle);
}
/**
@@ -2517,7 +2965,13 @@ public final class Settings {
* or not a valid float.
*/
public static float getFloat(ContentResolver cr, String name, float def) {
- String v = getString(cr, name);
+ return getFloatForUser(cr, name, def, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, float def,
+ int userHandle) {
+ String v = getStringForUser(cr, name, userHandle);
try {
return v != null ? Float.parseFloat(v) : def;
} catch (NumberFormatException e) {
@@ -2545,7 +2999,13 @@ public final class Settings {
*/
public static float getFloat(ContentResolver cr, String name)
throws SettingNotFoundException {
- String v = getString(cr, name);
+ return getFloatForUser(cr, name, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static float getFloatForUser(ContentResolver cr, String name, int userHandle)
+ throws SettingNotFoundException {
+ String v = getStringForUser(cr, name, userHandle);
if (v == null) {
throw new SettingNotFoundException(name);
}
@@ -2570,24 +3030,35 @@ public final class Settings {
* @return true if the value was set, false on database errors
*/
public static boolean putFloat(ContentResolver cr, String name, float value) {
- return putString(cr, name, Float.toString(value));
+ return putFloatForUser(cr, name, value, UserHandle.myUserId());
+ }
+
+ /** @hide */
+ public static boolean putFloatForUser(ContentResolver cr, String name, float value,
+ int userHandle) {
+ return putStringForUser(cr, name, Float.toString(value), userHandle);
}
/**
- * The content:// style URL for this table
+ * @deprecated Use {@link android.provider.Settings.Global#DEVELOPMENT_SETTINGS_ENABLED}
+ * instead
*/
- public static final Uri CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/secure");
+ @Deprecated
+ public static final String DEVELOPMENT_SETTINGS_ENABLED =
+ Global.DEVELOPMENT_SETTINGS_ENABLED;
/**
- * Whether user has enabled development settings.
+ * When the user has enable the option to have a "bug report" command
+ * in the power menu.
+ * @hide
*/
- public static final String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled";
+ public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
/**
- * Whether ADB is enabled.
+ * @deprecated Use {@link android.provider.Settings.Global#ADB_ENABLED} instead
*/
- public static final String ADB_ENABLED = "adb_enabled";
+ @Deprecated
+ public static final String ADB_ENABLED = Global.ADB_ENABLED;
/**
* Setting to allow mock locations and location provider status to be injected into the
@@ -2606,39 +3077,16 @@ public final class Settings {
public static final String ANDROID_ID = "android_id";
/**
- * Whether bluetooth is enabled/disabled
- * 0=disabled. 1=enabled.
+ * @deprecated Use {@link android.provider.Settings.Global#BLUETOOTH_ON} instead
*/
- public static final String BLUETOOTH_ON = "bluetooth_on";
-
- /**
- * Get the key that retrieves a bluetooth headset's priority.
- * @hide
- */
- public static final String getBluetoothHeadsetPriorityKey(String address) {
- return ("bluetooth_headset_priority_" + address.toUpperCase());
- }
-
- /**
- * Get the key that retrieves a bluetooth a2dp sink's priority.
- * @hide
- */
- public static final String getBluetoothA2dpSinkPriorityKey(String address) {
- return ("bluetooth_a2dp_sink_priority_" + address.toUpperCase());
- }
-
- /**
- * Get the key that retrieves a bluetooth Input Device's priority.
- * @hide
- */
- public static final String getBluetoothInputDevicePriorityKey(String address) {
- return ("bluetooth_input_device_priority_" + address.toUpperCase());
- }
+ @Deprecated
+ public static final String BLUETOOTH_ON = Global.BLUETOOTH_ON;
/**
- * Whether or not data roaming is enabled. (0 = false, 1 = true)
+ * @deprecated Use {@link android.provider.Settings.Global#DATA_ROAMING} instead
*/
- public static final String DATA_ROAMING = "data_roaming";
+ @Deprecated
+ public static final String DATA_ROAMING = Global.DATA_ROAMING;
/**
* Setting to record the input method used by default, holding the ID
@@ -2668,9 +3116,16 @@ public final class Settings {
"input_method_selector_visibility";
/**
- * Whether the device has been provisioned (0 = false, 1 = true)
+ * @deprecated Use {@link android.provider.Settings.Global#DEVICE_PROVISIONED} instead
+ */
+ @Deprecated
+ public static final String DEVICE_PROVISIONED = Global.DEVICE_PROVISIONED;
+
+ /**
+ * Whether the current user has been set up via setup wizard (0 = false, 1 = true)
+ * @hide
*/
- public static final String DEVICE_PROVISIONED = "device_provisioned";
+ public static final String USER_SETUP_COMPLETE = "user_setup_complete";
/**
* List of input methods that are currently enabled. This is a string
@@ -2688,54 +3143,19 @@ public final class Settings {
public static final String DISABLED_SYSTEM_INPUT_METHODS = "disabled_system_input_methods";
/**
- * Host name and port for global http proxy. Uses ':' seperator for between host and port
- * TODO - deprecate in favor of global_http_proxy_host, etc
- */
- public static final String HTTP_PROXY = "http_proxy";
-
- /**
- * Host name for global http proxy. Set via ConnectivityManager.
- * @hide
- */
- public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host";
-
- /**
- * Integer host port for global http proxy. Set via ConnectivityManager.
- * @hide
- */
- public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port";
-
- /**
- * Exclusion list for global proxy. This string contains a list of comma-separated
- * domains where the global proxy does not apply. Domains should be listed in a comma-
- * separated list. Example of acceptable formats: ".domain1.com,my.domain2.com"
- * Use ConnectivityManager to set/get.
- * @hide
- */
- public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST =
- "global_http_proxy_exclusion_list";
-
- /**
- * Enables the UI setting to allow the user to specify the global HTTP proxy
- * and associated exclusion list.
- * @hide
- */
- public static final String SET_GLOBAL_HTTP_PROXY = "set_global_http_proxy";
-
- /**
- * Setting for default DNS in case nobody suggests one
- * @hide
+ * Host name and port for global http proxy. Uses ':' seperator for
+ * between host and port.
+ *
+ * @deprecated Use {@link Global#HTTP_PROXY}
*/
- public static final String DEFAULT_DNS_SERVER = "default_dns_server";
+ @Deprecated
+ public static final String HTTP_PROXY = Global.HTTP_PROXY;
/**
- * Whether the package installer should allow installation of apps downloaded from
- * sources other than Google Play.
- *
- * 1 = allow installing from other sources
- * 0 = only allow installing from Google Play
+ * @deprecated Use {@link android.provider.Settings.Global#INSTALL_NON_MARKET_APPS} instead
*/
- public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
+ @Deprecated
+ public static final String INSTALL_NON_MARKET_APPS = Global.INSTALL_NON_MARKET_APPS;
/**
* Comma-separated list of location providers that activities may access.
@@ -2780,24 +3200,25 @@ public final class Settings {
public static final String LOCK_SCREEN_OWNER_INFO = "lock_screen_owner_info";
/**
- * This preference enables showing the owner info on LockScren.
+ * Id of the time appwidget on the lockscreen, or -1 if none
* @hide
*/
- public static final String LOCK_SCREEN_OWNER_INFO_ENABLED =
- "lock_screen_owner_info_enabled";
+ public static final String LOCK_SCREEN_STATUS_APPWIDGET_ID =
+ "lock_screen_status_appwidget_id";
/**
- * The saved value for WindowManagerService.setForcedDisplaySize().
- * Two integers separated by a comma. If unset, then use the real display size.
+ * Id of the user-selected appwidget on the lockscreen, or -1 if none
* @hide
*/
- public static final String DISPLAY_SIZE_FORCED = "display_size_forced";
+ public static final String LOCK_SCREEN_USER_SELECTED_APPWIDGET_ID =
+ "lock_screen_user_selected_appwidget_id";
/**
- * Whether assisted GPS should be enabled or not.
+ * This preference enables showing the owner info on LockScren.
* @hide
*/
- public static final String ASSISTED_GPS_ENABLED = "assisted_gps_enabled";
+ public static final String LOCK_SCREEN_OWNER_INFO_ENABLED =
+ "lock_screen_owner_info_enabled";
/**
* The Logging ID (a unique 64-bit value) as a hex string.
@@ -2809,34 +3230,10 @@ public final class Settings {
public static final String LOGGING_ID = "logging_id";
/**
- * User preference for which network(s) should be used. Only the
- * connectivity service should touch this.
- */
- public static final String NETWORK_PREFERENCE = "network_preference";
-
- /**
- * Used to disable Tethering on a device - defaults to true
- * @hide
- */
- public static final String TETHER_SUPPORTED = "tether_supported";
-
- /**
- * Used to require DUN APN on the device or not - defaults to a build config value
- * which defaults to false
- * @hide
- */
- public static final String TETHER_DUN_REQUIRED = "tether_dun_required";
-
- /**
- * Used to hold a gservices-provisioned apn value for DUN. If set, or the
- * corresponding build config values are set it will override the APN DB
- * values.
- * Consists of a comma seperated list of strings:
- * "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
- * note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN"
- * @hide
+ * @deprecated Use {@link android.provider.Settings.Global#NETWORK_PREFERENCE} instead
*/
- public static final String TETHER_DUN_APN = "tether_dun_apn";
+ @Deprecated
+ public static final String NETWORK_PREFERENCE = Global.NETWORK_PREFERENCE;
/**
* No longer supported.
@@ -2854,15 +3251,6 @@ public final class Settings {
public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
/**
- * A positive value indicates how often the SamplingProfiler
- * should take snapshots. Zero value means SamplingProfiler
- * is disabled.
- *
- * @hide
- */
- public static final String SAMPLING_PROFILER_MS = "sampling_profiler_ms";
-
- /**
* Settings classname to launch when Settings is clicked from All
* Applications. Needed because of user testing between the old
* and new Settings apps.
@@ -2871,15 +3259,16 @@ public final class Settings {
public static final String SETTINGS_CLASSNAME = "settings_classname";
/**
- * USB Mass Storage Enabled
+ * @deprecated Use {@link android.provider.Settings.Global#USB_MASS_STORAGE_ENABLED} instead
*/
- public static final String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
+ @Deprecated
+ public static final String USB_MASS_STORAGE_ENABLED = Global.USB_MASS_STORAGE_ENABLED;
/**
- * If this setting is set (to anything), then all references
- * to Gmail on the device must change to Google Mail.
+ * @deprecated Use {@link android.provider.Settings.Global#USE_GOOGLE_MAIL} instead
*/
- public static final String USE_GOOGLE_MAIL = "use_google_mail";
+ @Deprecated
+ public static final String USE_GOOGLE_MAIL = Global.USE_GOOGLE_MAIL;
/**
* If accessibility is enabled.
@@ -2898,7 +3287,7 @@ public final class Settings {
"enabled_accessibility_services";
/**
- * List of the accessibility services to which the user has graned
+ * List of the accessibility services to which the user has granted
* permission to put the device into touch exploration mode.
*
* @hide
@@ -2917,7 +3306,7 @@ public final class Settings {
* <p>
* Note: The JavaScript based screen-reader is served by the
* Google infrastructure and enable users with disabilities to
- * efficiantly navigate in and explore web content.
+ * efficiently navigate in and explore web content.
* </p>
* <p>
* This property represents a boolean value.
@@ -2929,7 +3318,7 @@ public final class Settings {
/**
* The URL for the injected JavaScript based screen-reader used
- * for providing accessiblity of content in WebView.
+ * for providing accessibility of content in WebView.
* <p>
* Note: The JavaScript based screen-reader is served by the
* Google infrastructure and enable users with disabilities to
@@ -2982,6 +3371,46 @@ public final class Settings {
"accessibility_web_content_key_bindings";
/**
+ * Setting that specifies whether the display magnification is enabled.
+ * Display magnifications allows the user to zoom in the display content
+ * and is targeted to low vision users. The current magnification scale
+ * is controlled by {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
+ "accessibility_display_magnification_enabled";
+
+ /**
+ * Setting that specifies what the display magnification scale is.
+ * Display magnifications allows the user to zoom in the display
+ * content and is targeted to low vision users. Whether a display
+ * magnification is performed is controlled by
+ * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED}
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE =
+ "accessibility_display_magnification_scale";
+
+ /**
+ * Setting that specifies whether the display magnification should be
+ * automatically updated. If this fearture is enabled the system will
+ * exit magnification mode or pan the viewport when a context change
+ * occurs. For example, on staring a new activity or rotating the screen,
+ * the system may zoom out so the user can see the new context he is in.
+ * Another example is on showing a window that is not visible in the
+ * magnified viewport the system may pan the viewport to make the window
+ * the has popped up so the user knows that the context has changed.
+ * Whether a screen magnification is performed is controlled by
+ * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED}
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE =
+ "accessibility_display_magnification_auto_update";
+
+ /**
* The timout for considering a press to be a long press in milliseconds.
* @hide
*/
@@ -3067,80 +3496,40 @@ public final class Settings {
public static final String TTS_ENABLED_PLUGINS = "tts_enabled_plugins";
/**
- * Whether to notify the user of open networks.
- * <p>
- * If not connected and the scan results have an open network, we will
- * put this notification up. If we attempt to connect to a network or
- * the open network(s) disappear, we remove the notification. When we
- * show the notification, we will not show it again for
- * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time.
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON}
+ * instead.
*/
+ @Deprecated
public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
- "wifi_networks_available_notification_on";
- /**
- * {@hide}
- */
- public static final String WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON =
- "wimax_networks_available_notification_on";
+ Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON;
/**
- * Delay (in seconds) before repeating the Wi-Fi networks available notification.
- * Connecting to a network will reset the timer.
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY}
+ * instead.
*/
+ @Deprecated
public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
- "wifi_networks_available_repeat_delay";
-
- /**
- * 802.11 country code in ISO 3166 format
- * @hide
- */
- public static final String WIFI_COUNTRY_CODE = "wifi_country_code";
-
-
- /**
- * When the number of open networks exceeds this number, the
- * least-recently-used excess networks will be removed.
- */
- public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
+ Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY;
/**
- * Whether the Wi-Fi should be on. Only the Wi-Fi service should touch this.
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_NUM_OPEN_NETWORKS_KEPT}
+ * instead.
*/
- public static final String WIFI_ON = "wifi_on";
-
- /**
- * Used to save the Wifi_ON state prior to tethering.
- * This state will be checked to restore Wifi after
- * the user turns off tethering.
- *
- * @hide
- */
- public static final String WIFI_SAVED_STATE = "wifi_saved_state";
-
- /**
- * AP SSID
- *
- * @hide
- */
- public static final String WIFI_AP_SSID = "wifi_ap_ssid";
-
- /**
- * AP security
- *
- * @hide
- */
- public static final String WIFI_AP_SECURITY = "wifi_ap_security";
+ @Deprecated
+ public static final String WIFI_NUM_OPEN_NETWORKS_KEPT =
+ Global.WIFI_NUM_OPEN_NETWORKS_KEPT;
/**
- * AP passphrase
- *
- * @hide
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_ON}
+ * instead.
*/
- public static final String WIFI_AP_PASSWD = "wifi_ap_passwd";
+ @Deprecated
+ public static final String WIFI_ON = Global.WIFI_ON;
/**
* The acceptable packet loss percentage (range 0 - 100) before trying
* another AP on the same network.
+ * @deprecated This setting is not used.
*/
@Deprecated
public static final String WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE =
@@ -3149,12 +3538,14 @@ public final class Settings {
/**
* The number of access points required for a network in order for the
* watchdog to monitor it.
+ * @deprecated This setting is not used.
*/
@Deprecated
public static final String WIFI_WATCHDOG_AP_COUNT = "wifi_watchdog_ap_count";
/**
* The delay between background checks.
+ * @deprecated This setting is not used.
*/
@Deprecated
public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS =
@@ -3163,6 +3554,7 @@ public final class Settings {
/**
* Whether the Wi-Fi watchdog is enabled for background checking even
* after it thinks the user has connected to a good access point.
+ * @deprecated This setting is not used.
*/
@Deprecated
public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED =
@@ -3170,6 +3562,7 @@ public final class Settings {
/**
* The timeout for a background ping
+ * @deprecated This setting is not used.
*/
@Deprecated
public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS =
@@ -3180,6 +3573,7 @@ public final class Settings {
* fail. Again, if these fail, they will *not* be used in packet loss
* calculation. For example, one network always seemed to time out for
* the first couple pings, so this is set to 3 by default.
+ * @deprecated This setting is not used.
*/
@Deprecated
public static final String WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT =
@@ -3190,1181 +3584,1934 @@ public final class Settings {
* If this number is reached, the watchdog will no longer monitor the
* initial connection state for the network. This is a safeguard for
* networks containing multiple APs whose DNS does not respond to pings.
+ * @deprecated This setting is not used.
*/
@Deprecated
public static final String WIFI_WATCHDOG_MAX_AP_CHECKS = "wifi_watchdog_max_ap_checks";
/**
- * Whether the Wi-Fi watchdog is enabled.
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_WATCHDOG_ON} instead
*/
+ @Deprecated
public static final String WIFI_WATCHDOG_ON = "wifi_watchdog_on";
/**
* A comma-separated list of SSIDs for which the Wi-Fi watchdog should be enabled.
+ * @deprecated This setting is not used.
*/
@Deprecated
public static final String WIFI_WATCHDOG_WATCH_LIST = "wifi_watchdog_watch_list";
/**
* The number of pings to test if an access point is a good connection.
+ * @deprecated This setting is not used.
*/
@Deprecated
public static final String WIFI_WATCHDOG_PING_COUNT = "wifi_watchdog_ping_count";
/**
* The delay between pings.
+ * @deprecated This setting is not used.
*/
@Deprecated
public static final String WIFI_WATCHDOG_PING_DELAY_MS = "wifi_watchdog_ping_delay_ms";
/**
* The timeout per ping.
+ * @deprecated This setting is not used.
*/
@Deprecated
public static final String WIFI_WATCHDOG_PING_TIMEOUT_MS = "wifi_watchdog_ping_timeout_ms";
/**
- * ms delay before rechecking an 'online' wifi connection when it is thought to be unstable.
- * @hide
+ * @deprecated Use
+ * {@link android.provider.Settings.Global#WIFI_MAX_DHCP_RETRY_COUNT} instead
*/
- public static final String WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS =
- "wifi_watchdog_arp_interval_ms";
+ @Deprecated
+ public static final String WIFI_MAX_DHCP_RETRY_COUNT = Global.WIFI_MAX_DHCP_RETRY_COUNT;
/**
- * ms delay interval between rssi polling when the signal is known to be weak
- * @hide
+ * @deprecated Use
+ * {@link android.provider.Settings.Global#WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS} instead
*/
- public static final String WIFI_WATCHDOG_RSSI_FETCH_INTERVAL_MS =
- "wifi_watchdog_rssi_fetch_interval_ms";
-
+ @Deprecated
+ public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS =
+ Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS;
/**
- * ms delay before rechecking a connect SSID for walled garden with a http download.
- * @hide
+ * Whether background data usage is allowed.
+ *
+ * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH},
+ * availability of background data depends on several
+ * combined factors. When background data is unavailable,
+ * {@link ConnectivityManager#getActiveNetworkInfo()} will
+ * now appear disconnected.
*/
- public static final String WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS =
- "wifi_watchdog_walled_garden_interval_ms";
+ @Deprecated
+ public static final String BACKGROUND_DATA = "background_data";
/**
- * Number of ARP pings per check.
- * @hide
+ * Origins for which browsers should allow geolocation by default.
+ * The value is a space-separated list of origins.
*/
- public static final String WIFI_WATCHDOG_NUM_ARP_PINGS = "wifi_watchdog_num_arp_pings";
+ public static final String ALLOWED_GEOLOCATION_ORIGINS
+ = "allowed_geolocation_origins";
/**
- * Minimum number of responses to the arp pings to consider the test 'successful'.
+ * The preferred TTY mode 0 = TTy Off, CDMA default
+ * 1 = TTY Full
+ * 2 = TTY HCO
+ * 3 = TTY VCO
* @hide
*/
- public static final String WIFI_WATCHDOG_MIN_ARP_RESPONSES =
- "wifi_watchdog_min_arp_responses";
+ public static final String PREFERRED_TTY_MODE =
+ "preferred_tty_mode";
/**
- * Timeout on ARP pings
+ * Whether the enhanced voice privacy mode is enabled.
+ * 0 = normal voice privacy
+ * 1 = enhanced voice privacy
* @hide
*/
- public static final String WIFI_WATCHDOG_ARP_PING_TIMEOUT_MS =
- "wifi_watchdog_arp_ping_timeout_ms";
+ public static final String ENHANCED_VOICE_PRIVACY_ENABLED = "enhanced_voice_privacy_enabled";
/**
- * Setting to turn off poor network avoidance on Wi-Fi. Feature is enabled by default and
- * the setting needs to be set to 0 to disable it.
+ * Whether the TTY mode mode is enabled.
+ * 0 = disabled
+ * 1 = enabled
* @hide
*/
- public static final String WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED =
- "wifi_watchdog_poor_network_test_enabled";
+ public static final String TTY_MODE_ENABLED = "tty_mode_enabled";
/**
- * Setting to turn off walled garden test on Wi-Fi. Feature is enabled by default and
- * the setting needs to be set to 0 to disable it.
+ * Controls whether settings backup is enabled.
+ * Type: int ( 0 = disabled, 1 = enabled )
* @hide
*/
- public static final String WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED =
- "wifi_watchdog_walled_garden_test_enabled";
+ public static final String BACKUP_ENABLED = "backup_enabled";
/**
- * The URL used for walled garden check upon a new conection. WifiWatchdogService
- * fetches the URL and checks to see if {@link #WIFI_WATCHDOG_WALLED_GARDEN_PATTERN}
- * is not part of the title string to notify the user on the presence of a walled garden.
+ * Controls whether application data is automatically restored from backup
+ * at install time.
+ * Type: int ( 0 = disabled, 1 = enabled )
* @hide
*/
- public static final String WIFI_WATCHDOG_WALLED_GARDEN_URL =
- "wifi_watchdog_walled_garden_url";
+ public static final String BACKUP_AUTO_RESTORE = "backup_auto_restore";
/**
- * The maximum number of times we will retry a connection to an access
- * point for which we have failed in acquiring an IP address from DHCP.
- * A value of N means that we will make N+1 connection attempts in all.
+ * Indicates whether settings backup has been fully provisioned.
+ * Type: int ( 0 = unprovisioned, 1 = fully provisioned )
+ * @hide
*/
- public static final String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
+ public static final String BACKUP_PROVISIONED = "backup_provisioned";
/**
- * The operational wifi frequency band
- * Set to one of {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO},
- * {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ} or
- * {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ}
- *
+ * Component of the transport to use for backup/restore.
* @hide
*/
- public static final String WIFI_FREQUENCY_BAND = "wifi_frequency_band";
+ public static final String BACKUP_TRANSPORT = "backup_transport";
/**
- * The Wi-Fi peer-to-peer device name
+ * Version for which the setup wizard was last shown. Bumped for
+ * each release when there is new setup information to show.
* @hide
*/
- public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
+ public static final String LAST_SETUP_SHOWN = "last_setup_shown";
/**
- * Maximum amount of time in milliseconds to hold a wakelock while waiting for mobile
- * data connectivity to be established after a disconnect from Wi-Fi.
+ * The interval in milliseconds after which Wi-Fi is considered idle.
+ * When idle, it is possible for the device to be switched from Wi-Fi to
+ * the mobile data network.
+ * @hide
+ * @deprecated Use {@link android.provider.Settings.Global#WIFI_IDLE_MS}
+ * instead.
*/
- public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS =
- "wifi_mobile_data_transition_wakelock_timeout_ms";
+ @Deprecated
+ public static final String WIFI_IDLE_MS = Global.WIFI_IDLE_MS;
/**
- * Whether network service discovery is enabled.
+ * The global search provider chosen by the user (if multiple global
+ * search providers are installed). This will be the provider returned
+ * by {@link SearchManager#getGlobalSearchActivity()} if it's still
+ * installed. This setting is stored as a flattened component name as
+ * per {@link ComponentName#flattenToString()}.
+ *
* @hide
*/
- public static final String NSD_ON = "nsd_on";
+ public static final String SEARCH_GLOBAL_SEARCH_ACTIVITY =
+ "search_global_search_activity";
/**
- * Whether background data usage is allowed by the user. See
- * ConnectivityManager for more info.
+ * The number of promoted sources in GlobalSearch.
+ * @hide
*/
- @Deprecated
- public static final String BACKGROUND_DATA = "background_data";
-
+ public static final String SEARCH_NUM_PROMOTED_SOURCES = "search_num_promoted_sources";
/**
- * Origins for which browsers should allow geolocation by default.
- * The value is a space-separated list of origins.
+ * The maximum number of suggestions returned by GlobalSearch.
+ * @hide
*/
- public static final String ALLOWED_GEOLOCATION_ORIGINS
- = "allowed_geolocation_origins";
-
+ public static final String SEARCH_MAX_RESULTS_TO_DISPLAY = "search_max_results_to_display";
/**
- * Whether mobile data connections are allowed by the user. See
- * ConnectivityManager for more info.
+ * The number of suggestions GlobalSearch will ask each non-web search source for.
* @hide
*/
- public static final String MOBILE_DATA = "mobile_data";
-
+ public static final String SEARCH_MAX_RESULTS_PER_SOURCE = "search_max_results_per_source";
/**
- * The CDMA roaming mode 0 = Home Networks, CDMA default
- * 1 = Roaming on Affiliated networks
- * 2 = Roaming on any networks
+ * The number of suggestions the GlobalSearch will ask the web search source for.
* @hide
*/
- public static final String CDMA_ROAMING_MODE = "roaming_settings";
-
+ public static final String SEARCH_WEB_RESULTS_OVERRIDE_LIMIT =
+ "search_web_results_override_limit";
/**
- * The CDMA subscription mode 0 = RUIM/SIM (default)
- * 1 = NV
+ * The number of milliseconds that GlobalSearch will wait for suggestions from
+ * promoted sources before continuing with all other sources.
* @hide
*/
- public static final String CDMA_SUBSCRIPTION_MODE = "subscription_mode";
-
+ public static final String SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS =
+ "search_promoted_source_deadline_millis";
/**
- * The preferred network mode 7 = Global
- * 6 = EvDo only
- * 5 = CDMA w/o EvDo
- * 4 = CDMA / EvDo auto
- * 3 = GSM / WCDMA auto
- * 2 = WCDMA only
- * 1 = GSM only
- * 0 = GSM / WCDMA preferred
+ * The number of milliseconds before GlobalSearch aborts search suggesiton queries.
* @hide
*/
- public static final String PREFERRED_NETWORK_MODE =
- "preferred_network_mode";
-
+ public static final String SEARCH_SOURCE_TIMEOUT_MILLIS = "search_source_timeout_millis";
/**
- * The preferred TTY mode 0 = TTy Off, CDMA default
- * 1 = TTY Full
- * 2 = TTY HCO
- * 3 = TTY VCO
+ * The maximum number of milliseconds that GlobalSearch shows the previous results
+ * after receiving a new query.
* @hide
*/
- public static final String PREFERRED_TTY_MODE =
- "preferred_tty_mode";
-
-
+ public static final String SEARCH_PREFILL_MILLIS = "search_prefill_millis";
/**
- * CDMA Cell Broadcast SMS
- * 0 = CDMA Cell Broadcast SMS disabled
- * 1 = CDMA Cell Broadcast SMS enabled
+ * The maximum age of log data used for shortcuts in GlobalSearch.
* @hide
*/
- public static final String CDMA_CELL_BROADCAST_SMS =
- "cdma_cell_broadcast_sms";
-
+ public static final String SEARCH_MAX_STAT_AGE_MILLIS = "search_max_stat_age_millis";
/**
- * The cdma subscription 0 = Subscription from RUIM, when available
- * 1 = Subscription from NV
+ * The maximum age of log data used for source ranking in GlobalSearch.
* @hide
*/
- public static final String PREFERRED_CDMA_SUBSCRIPTION =
- "preferred_cdma_subscription";
-
+ public static final String SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS =
+ "search_max_source_event_age_millis";
/**
- * Whether the enhanced voice privacy mode is enabled.
- * 0 = normal voice privacy
- * 1 = enhanced voice privacy
+ * The minimum number of impressions needed to rank a source in GlobalSearch.
* @hide
*/
- public static final String ENHANCED_VOICE_PRIVACY_ENABLED = "enhanced_voice_privacy_enabled";
-
+ public static final String SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING =
+ "search_min_impressions_for_source_ranking";
/**
- * Whether the TTY mode mode is enabled.
- * 0 = disabled
- * 1 = enabled
+ * The minimum number of clicks needed to rank a source in GlobalSearch.
* @hide
*/
- public static final String TTY_MODE_ENABLED = "tty_mode_enabled";
-
+ public static final String SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING =
+ "search_min_clicks_for_source_ranking";
/**
- * The number of milliseconds to delay before sending out Connectivyt Change broadcasts
+ * The maximum number of shortcuts shown by GlobalSearch.
* @hide
*/
- public static final String CONNECTIVITY_CHANGE_DELAY = "connectivity_change_delay";
-
+ public static final String SEARCH_MAX_SHORTCUTS_RETURNED = "search_max_shortcuts_returned";
/**
- * Default value for CONNECTIVITY_CHANGE_DELAY in milliseconds.
+ * The size of the core thread pool for suggestion queries in GlobalSearch.
* @hide
*/
- public static final int CONNECTIVITY_CHANGE_DELAY_DEFAULT = 3000;
-
+ public static final String SEARCH_QUERY_THREAD_CORE_POOL_SIZE =
+ "search_query_thread_core_pool_size";
/**
- * Controls whether settings backup is enabled.
- * Type: int ( 0 = disabled, 1 = enabled )
+ * The maximum size of the thread pool for suggestion queries in GlobalSearch.
* @hide
*/
- public static final String BACKUP_ENABLED = "backup_enabled";
-
+ public static final String SEARCH_QUERY_THREAD_MAX_POOL_SIZE =
+ "search_query_thread_max_pool_size";
/**
- * Controls whether application data is automatically restored from backup
- * at install time.
- * Type: int ( 0 = disabled, 1 = enabled )
+ * The size of the core thread pool for shortcut refreshing in GlobalSearch.
* @hide
*/
- public static final String BACKUP_AUTO_RESTORE = "backup_auto_restore";
-
+ public static final String SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE =
+ "search_shortcut_refresh_core_pool_size";
/**
- * Indicates whether settings backup has been fully provisioned.
- * Type: int ( 0 = unprovisioned, 1 = fully provisioned )
+ * The maximum size of the thread pool for shortcut refreshing in GlobalSearch.
* @hide
*/
- public static final String BACKUP_PROVISIONED = "backup_provisioned";
-
+ public static final String SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE =
+ "search_shortcut_refresh_max_pool_size";
/**
- * Component of the transport to use for backup/restore.
+ * The maximun time that excess threads in the GlobalSeach thread pools will
+ * wait before terminating.
* @hide
*/
- public static final String BACKUP_TRANSPORT = "backup_transport";
-
+ public static final String SEARCH_THREAD_KEEPALIVE_SECONDS =
+ "search_thread_keepalive_seconds";
/**
- * Version for which the setup wizard was last shown. Bumped for
- * each release when there is new setup information to show.
+ * The maximum number of concurrent suggestion queries to each source.
* @hide
*/
- public static final String LAST_SETUP_SHOWN = "last_setup_shown";
+ public static final String SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT =
+ "search_per_source_concurrent_query_limit";
/**
- * How frequently (in seconds) to check the memory status of the
- * device.
+ * Whether or not alert sounds are played on MountService events. (0 = false, 1 = true)
* @hide
*/
- public static final String MEMCHECK_INTERVAL = "memcheck_interval";
+ public static final String MOUNT_PLAY_NOTIFICATION_SND = "mount_play_not_snd";
/**
- * Max frequency (in seconds) to log memory check stats, in realtime
- * seconds. This allows for throttling of logs when the device is
- * running for large amounts of time.
+ * Whether or not UMS auto-starts on UMS host detection. (0 = false, 1 = true)
* @hide
*/
- public static final String MEMCHECK_LOG_REALTIME_INTERVAL =
- "memcheck_log_realtime_interval";
+ public static final String MOUNT_UMS_AUTOSTART = "mount_ums_autostart";
/**
- * Boolean indicating whether rebooting due to system memory checks
- * is enabled.
+ * Whether or not a notification is displayed on UMS host detection. (0 = false, 1 = true)
* @hide
*/
- public static final String MEMCHECK_SYSTEM_ENABLED = "memcheck_system_enabled";
+ public static final String MOUNT_UMS_PROMPT = "mount_ums_prompt";
/**
- * How many bytes the system process must be below to avoid scheduling
- * a soft reboot. This reboot will happen when it is next determined
- * to be a good time.
+ * Whether or not a notification is displayed while UMS is enabled. (0 = false, 1 = true)
* @hide
*/
- public static final String MEMCHECK_SYSTEM_SOFT_THRESHOLD = "memcheck_system_soft";
+ public static final String MOUNT_UMS_NOTIFY_ENABLED = "mount_ums_notify_enabled";
/**
- * How many bytes the system process must be below to avoid scheduling
- * a hard reboot. This reboot will happen immediately.
+ * If nonzero, ANRs in invisible background processes bring up a dialog.
+ * Otherwise, the process will be silently killed.
* @hide
*/
- public static final String MEMCHECK_SYSTEM_HARD_THRESHOLD = "memcheck_system_hard";
+ public static final String ANR_SHOW_BACKGROUND = "anr_show_background";
/**
- * How many bytes the phone process must be below to avoid scheduling
- * a soft restart. This restart will happen when it is next determined
- * to be a good time.
+ * The {@link ComponentName} string of the service to be used as the voice recognition
+ * service.
+ *
* @hide
*/
- public static final String MEMCHECK_PHONE_SOFT_THRESHOLD = "memcheck_phone_soft";
+ public static final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service";
+
/**
- * How many bytes the phone process must be below to avoid scheduling
- * a hard restart. This restart will happen immediately.
+ * The {@link ComponentName} string of the selected spell checker service which is
+ * one of the services managed by the text service manager.
+ *
* @hide
*/
- public static final String MEMCHECK_PHONE_HARD_THRESHOLD = "memcheck_phone_hard";
+ public static final String SELECTED_SPELL_CHECKER = "selected_spell_checker";
/**
- * Boolean indicating whether restarting the phone process due to
- * memory checks is enabled.
+ * The {@link ComponentName} string of the selected subtype of the selected spell checker
+ * service which is one of the services managed by the text service manager.
+ *
* @hide
*/
- public static final String MEMCHECK_PHONE_ENABLED = "memcheck_phone_enabled";
+ public static final String SELECTED_SPELL_CHECKER_SUBTYPE =
+ "selected_spell_checker_subtype";
/**
- * First time during the day it is okay to kill processes
- * or reboot the device due to low memory situations. This number is
- * in seconds since midnight.
+ * The {@link ComponentName} string whether spell checker is enabled or not.
+ *
* @hide
*/
- public static final String MEMCHECK_EXEC_START_TIME = "memcheck_exec_start_time";
+ public static final String SPELL_CHECKER_ENABLED = "spell_checker_enabled";
/**
- * Last time during the day it is okay to kill processes
- * or reboot the device due to low memory situations. This number is
- * in seconds since midnight.
+ * What happens when the user presses the Power button while in-call
+ * and the screen is on.<br/>
+ * <b>Values:</b><br/>
+ * 1 - The Power button turns off the screen and locks the device. (Default behavior)<br/>
+ * 2 - The Power button hangs up the current call.<br/>
+ *
* @hide
*/
- public static final String MEMCHECK_EXEC_END_TIME = "memcheck_exec_end_time";
+ public static final String INCALL_POWER_BUTTON_BEHAVIOR = "incall_power_button_behavior";
/**
- * How long the screen must have been off in order to kill processes
- * or reboot. This number is in seconds. A value of -1 means to
- * entirely disregard whether the screen is on.
+ * INCALL_POWER_BUTTON_BEHAVIOR value for "turn off screen".
* @hide
*/
- public static final String MEMCHECK_MIN_SCREEN_OFF = "memcheck_min_screen_off";
+ public static final int INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF = 0x1;
/**
- * How much time there must be until the next alarm in order to kill processes
- * or reboot. This number is in seconds. Note: this value must be
- * smaller than {@link #MEMCHECK_RECHECK_INTERVAL} or else it will
- * always see an alarm scheduled within its time.
+ * INCALL_POWER_BUTTON_BEHAVIOR value for "hang up".
* @hide
*/
- public static final String MEMCHECK_MIN_ALARM = "memcheck_min_alarm";
+ public static final int INCALL_POWER_BUTTON_BEHAVIOR_HANGUP = 0x2;
/**
- * How frequently to check whether it is a good time to restart things,
- * if the device is in a bad state. This number is in seconds. Note:
- * this value must be larger than {@link #MEMCHECK_MIN_ALARM} or else
- * the alarm to schedule the recheck will always appear within the
- * minimum "do not execute now" time.
+ * INCALL_POWER_BUTTON_BEHAVIOR default value.
* @hide
*/
- public static final String MEMCHECK_RECHECK_INTERVAL = "memcheck_recheck_interval";
+ public static final int INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT =
+ INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF;
/**
- * How frequently (in DAYS) to reboot the device. If 0, no reboots
- * will occur.
+ * The current night mode that has been selected by the user. Owned
+ * and controlled by UiModeManagerService. Constants are as per
+ * UiModeManager.
* @hide
*/
- public static final String REBOOT_INTERVAL = "reboot_interval";
+ public static final String UI_NIGHT_MODE = "ui_night_mode";
/**
- * First time during the day it is okay to force a reboot of the
- * device (if REBOOT_INTERVAL is set). This number is
- * in seconds since midnight.
+ * Whether screensavers are enabled.
* @hide
*/
- public static final String REBOOT_START_TIME = "reboot_start_time";
+ public static final String SCREENSAVER_ENABLED = "screensaver_enabled";
/**
- * The window of time (in seconds) after each REBOOT_INTERVAL in which
- * a reboot can be executed. If 0, a reboot will always be executed at
- * exactly the given time. Otherwise, it will only be executed if
- * the device is idle within the window.
+ * The user's chosen screensaver components.
+ *
+ * These will be launched by the PhoneWindowManager after a timeout when not on
+ * battery, or upon dock insertion (if SCREENSAVER_ACTIVATE_ON_DOCK is set to 1).
* @hide
*/
- public static final String REBOOT_WINDOW = "reboot_window";
+ public static final String SCREENSAVER_COMPONENTS = "screensaver_components";
/**
- * Threshold values for the duration and level of a discharge cycle, under
- * which we log discharge cycle info.
+ * If screensavers are enabled, whether the screensaver should be automatically launched
+ * when the device is inserted into a (desk) dock.
* @hide
*/
- public static final String BATTERY_DISCHARGE_DURATION_THRESHOLD =
- "battery_discharge_duration_threshold";
- /** @hide */
- public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold";
+ public static final String SCREENSAVER_ACTIVATE_ON_DOCK = "screensaver_activate_on_dock";
/**
- * Flag for allowing ActivityManagerService to send ACTION_APP_ERROR intents
- * on application crashes and ANRs. If this is disabled, the crash/ANR dialog
- * will never display the "Report" button.
- * Type: int ( 0 = disallow, 1 = allow )
+ * If screensavers are enabled, whether the screensaver should be automatically launched
+ * when the screen times out when not on battery.
* @hide
*/
- public static final String SEND_ACTION_APP_ERROR = "send_action_app_error";
+ public static final String SCREENSAVER_ACTIVATE_ON_SLEEP = "screensaver_activate_on_sleep";
/**
- * Nonzero causes Log.wtf() to crash.
+ * If screensavers are enabled, the default screensaver component.
* @hide
*/
- public static final String WTF_IS_FATAL = "wtf_is_fatal";
+ public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component";
/**
- * Maximum age of entries kept by {@link com.android.internal.os.IDropBoxManagerService}.
+ * This are the settings to be backed up.
+ *
+ * NOTE: Settings are backed up and restored in the order they appear
+ * in this array. If you have one setting depending on another,
+ * make sure that they are ordered appropriately.
+ *
* @hide
*/
- public static final String DROPBOX_AGE_SECONDS =
- "dropbox_age_seconds";
+ public static final String[] SETTINGS_TO_BACKUP = {
+ ADB_ENABLED,
+ BUGREPORT_IN_POWER_MENU,
+ ALLOW_MOCK_LOCATION,
+ PARENTAL_CONTROL_ENABLED,
+ PARENTAL_CONTROL_REDIRECT_URL,
+ USB_MASS_STORAGE_ENABLED,
+ ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
+ ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
+ ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
+ ACCESSIBILITY_SCRIPT_INJECTION,
+ BACKUP_AUTO_RESTORE,
+ ENABLED_ACCESSIBILITY_SERVICES,
+ TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ TOUCH_EXPLORATION_ENABLED,
+ ACCESSIBILITY_ENABLED,
+ ACCESSIBILITY_SPEAK_PASSWORD,
+ TTS_USE_DEFAULTS,
+ TTS_DEFAULT_RATE,
+ TTS_DEFAULT_PITCH,
+ TTS_DEFAULT_SYNTH,
+ TTS_DEFAULT_LANG,
+ TTS_DEFAULT_COUNTRY,
+ TTS_ENABLED_PLUGINS,
+ TTS_DEFAULT_LOCALE,
+ WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
+ WIFI_NUM_OPEN_NETWORKS_KEPT,
+ MOUNT_PLAY_NOTIFICATION_SND,
+ MOUNT_UMS_AUTOSTART,
+ MOUNT_UMS_PROMPT,
+ MOUNT_UMS_NOTIFY_ENABLED,
+ UI_NIGHT_MODE,
+ LOCK_SCREEN_OWNER_INFO,
+ LOCK_SCREEN_OWNER_INFO_ENABLED
+ };
+
/**
- * Maximum number of entry files which {@link com.android.internal.os.IDropBoxManagerService} will keep around.
- * @hide
+ * 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
*/
- public static final String DROPBOX_MAX_FILES =
- "dropbox_max_files";
+ public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
+ String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED);
+ return TextUtils.delimitedStringContains(allowedProviders, ',', provider);
+ }
+
/**
- * Maximum amount of disk space used by {@link com.android.internal.os.IDropBoxManagerService} no matter what.
- * @hide
+ * 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
*/
- public static final String DROPBOX_QUOTA_KB =
- "dropbox_quota_kb";
+ public static final void setLocationProviderEnabled(ContentResolver cr,
+ String provider, boolean enabled) {
+ // 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;
+ }
+ putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider);
+ }
+ }
+
+ /**
+ * Global system settings, containing preferences that always apply identically
+ * to all defined users. Applications can read these but are not allowed to write;
+ * like the "Secure" settings, these are for preferences that the user must
+ * explicitly modify through the system UI or specialized APIs for those values.
+ */
+ public static final class Global extends NameValueTable {
+ public static final String SYS_PROP_SETTING_VERSION = "sys.settings_global_version";
+
/**
- * Percent of free disk (excluding reserve) which {@link com.android.internal.os.IDropBoxManagerService} will use.
- * @hide
+ * The content:// style URL for global secure settings items. Not public.
*/
- public static final String DROPBOX_QUOTA_PERCENT =
- "dropbox_quota_percent";
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");
+
/**
- * Percent of total disk which {@link com.android.internal.os.IDropBoxManagerService} will never dip into.
+ * Setting whether the global gesture for enabling accessibility is enabled.
+ * If this gesture is enabled the user will be able to perfrom it to enable
+ * the accessibility state without visiting the settings app.
* @hide
*/
- public static final String DROPBOX_RESERVE_PERCENT =
- "dropbox_reserve_percent";
+ public static final String ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED =
+ "enable_accessibility_global_gesture_enabled";
+
/**
- * Prefix for per-tag dropbox disable/enable settings.
- * @hide
+ * Whether Airplane Mode is on.
*/
- public static final String DROPBOX_TAG_PREFIX =
- "dropbox:";
+ public static final String AIRPLANE_MODE_ON = "airplane_mode_on";
+
/**
- * Lines of logcat to include with system crash/ANR/etc. reports,
- * as a prefix of the dropbox tag of the report type.
- * For example, "logcat_for_system_server_anr" controls the lines
- * of logcat captured with system server ANR reports. 0 to disable.
- * @hide
+ * Constant for use in AIRPLANE_MODE_RADIOS to specify Bluetooth radio.
*/
- public static final String ERROR_LOGCAT_PREFIX =
- "logcat_for_";
-
+ public static final String RADIO_BLUETOOTH = "bluetooth";
/**
- * Screen timeout in milliseconds corresponding to the
- * PowerManager's POKE_LOCK_SHORT_TIMEOUT flag (i.e. the fastest
- * possible screen timeout behavior.)
- * @hide
+ * Constant for use in AIRPLANE_MODE_RADIOS to specify Wi-Fi radio.
*/
- public static final String SHORT_KEYLIGHT_DELAY_MS =
- "short_keylight_delay_ms";
+ public static final String RADIO_WIFI = "wifi";
/**
- * The interval in minutes after which the amount of free storage left on the
- * device is logged to the event log
- * @hide
+ * {@hide}
*/
- public static final String SYS_FREE_STORAGE_LOG_INTERVAL =
- "sys_free_storage_log_interval";
-
+ public static final String RADIO_WIMAX = "wimax";
/**
- * Threshold for the amount of change in disk free space required to report the amount of
- * free space. Used to prevent spamming the logs when the disk free space isn't changing
- * frequently.
- * @hide
+ * Constant for use in AIRPLANE_MODE_RADIOS to specify Cellular radio.
*/
- public static final String DISK_FREE_CHANGE_REPORTING_THRESHOLD =
- "disk_free_change_reporting_threshold";
-
+ public static final String RADIO_CELL = "cell";
/**
- * Minimum percentage of free storage on the device that is used to determine if
- * the device is running low on storage. The default is 10.
- * <p>Say this value is set to 10, the device is considered running low on storage
- * if 90% or more of the device storage is filled up.
- * @hide
+ * Constant for use in AIRPLANE_MODE_RADIOS to specify NFC radio.
*/
- public static final String SYS_STORAGE_THRESHOLD_PERCENTAGE =
- "sys_storage_threshold_percentage";
+ public static final String RADIO_NFC = "nfc";
/**
- * Maximum byte size of the low storage threshold. This is to ensure
- * that {@link #SYS_STORAGE_THRESHOLD_PERCENTAGE} does not result in
- * an overly large threshold for large storage devices. Currently this
- * must be less than 2GB. This default is 500MB.
- * @hide
+ * A comma separated list of radios that need to be disabled when airplane mode
+ * is on. This overrides WIFI_ON and BLUETOOTH_ON, if Wi-Fi and bluetooth are
+ * included in the comma separated list.
*/
- public static final String SYS_STORAGE_THRESHOLD_MAX_BYTES =
- "sys_storage_threshold_max_bytes";
+ public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios";
/**
- * Minimum bytes of free storage on the device before the data
- * partition is considered full. By default, 1 MB is reserved
- * to avoid system-wide SQLite disk full exceptions.
- * @hide
+ * A comma separated list of radios that should to be disabled when airplane mode
+ * is on, but can be manually reenabled by the user. For example, if RADIO_WIFI is
+ * added to both AIRPLANE_MODE_RADIOS and AIRPLANE_MODE_TOGGLEABLE_RADIOS, then Wifi
+ * will be turned off when entering airplane mode, but the user will be able to reenable
+ * Wifi in the Settings app.
+ *
+ * {@hide}
*/
- public static final String SYS_STORAGE_FULL_THRESHOLD_BYTES =
- "sys_storage_full_threshold_bytes";
+ public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
/**
- * The interval in milliseconds after which Wi-Fi is considered idle.
- * When idle, it is possible for the device to be switched from Wi-Fi to
- * the mobile data network.
- * @hide
+ * The policy for deciding when Wi-Fi should go to sleep (which will in
+ * turn switch to using the mobile data as an Internet connection).
+ * <p>
+ * Set to one of {@link #WIFI_SLEEP_POLICY_DEFAULT},
+ * {@link #WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED}, or
+ * {@link #WIFI_SLEEP_POLICY_NEVER}.
*/
- public static final String WIFI_IDLE_MS = "wifi_idle_ms";
+ public static final String WIFI_SLEEP_POLICY = "wifi_sleep_policy";
/**
- * The interval in milliseconds to issue wake up scans when wifi needs
- * to connect. This is necessary to connect to an access point when
- * device is on the move and the screen is off.
- * @hide
+ * Value for {@link #WIFI_SLEEP_POLICY} to use the default Wi-Fi sleep
+ * policy, which is to sleep shortly after the turning off
+ * according to the {@link #STAY_ON_WHILE_PLUGGED_IN} setting.
*/
- public static final String WIFI_FRAMEWORK_SCAN_INTERVAL_MS =
- "wifi_framework_scan_interval_ms";
+ public static final int WIFI_SLEEP_POLICY_DEFAULT = 0;
/**
- * The interval in milliseconds to scan as used by the wifi supplicant
- * @hide
+ * Value for {@link #WIFI_SLEEP_POLICY} to use the default policy when
+ * the device is on battery, and never go to sleep when the device is
+ * plugged in.
*/
- public static final String WIFI_SUPPLICANT_SCAN_INTERVAL_MS =
- "wifi_supplicant_scan_interval_ms";
+ public static final int WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED = 1;
/**
- * The interval in milliseconds at which to check packet counts on the
- * mobile data interface when screen is on, to detect possible data
- * connection problems.
- * @hide
+ * Value for {@link #WIFI_SLEEP_POLICY} to never go to sleep.
*/
- public static final String PDP_WATCHDOG_POLL_INTERVAL_MS =
- "pdp_watchdog_poll_interval_ms";
+ public static final int WIFI_SLEEP_POLICY_NEVER = 2;
/**
- * The interval in milliseconds at which to check packet counts on the
- * mobile data interface when screen is off, to detect possible data
- * connection problems.
- * @hide
+ * Value to specify if the user prefers the date, time and time zone
+ * to be automatically fetched from the network (NITZ). 1=yes, 0=no
*/
- public static final String PDP_WATCHDOG_LONG_POLL_INTERVAL_MS =
- "pdp_watchdog_long_poll_interval_ms";
+ public static final String AUTO_TIME = "auto_time";
/**
- * The interval in milliseconds at which to check packet counts on the
- * mobile data interface after {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT}
- * outgoing packets has been reached without incoming packets.
- * @hide
+ * Value to specify if the user prefers the time zone
+ * to be automatically fetched from the network (NITZ). 1=yes, 0=no
*/
- public static final String PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS =
- "pdp_watchdog_error_poll_interval_ms";
+ public static final String AUTO_TIME_ZONE = "auto_time_zone";
/**
- * The number of outgoing packets sent without seeing an incoming packet
- * that triggers a countdown (of {@link #PDP_WATCHDOG_ERROR_POLL_COUNT}
- * device is logged to the event log
+ * URI for the car dock "in" event sound.
* @hide
*/
- public static final String PDP_WATCHDOG_TRIGGER_PACKET_COUNT =
- "pdp_watchdog_trigger_packet_count";
+ public static final String CAR_DOCK_SOUND = "car_dock_sound";
/**
- * The number of polls to perform (at {@link #PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS})
- * after hitting {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT} before
- * attempting data connection recovery.
+ * URI for the car dock "out" event sound.
* @hide
*/
- public static final String PDP_WATCHDOG_ERROR_POLL_COUNT =
- "pdp_watchdog_error_poll_count";
+ public static final String CAR_UNDOCK_SOUND = "car_undock_sound";
/**
- * The number of failed PDP reset attempts before moving to something more
- * drastic: re-registering to the network.
+ * URI for the desk dock "in" event sound.
* @hide
*/
- public static final String PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT =
- "pdp_watchdog_max_pdp_reset_fail_count";
+ public static final String DESK_DOCK_SOUND = "desk_dock_sound";
/**
- * The number of milliseconds to delay when checking for data stalls during
- * non-aggressive detection. (screen is turned off.)
+ * URI for the desk dock "out" event sound.
* @hide
*/
- public static final String DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS =
- "data_stall_alarm_non_aggressive_delay_in_ms";
+ public static final String DESK_UNDOCK_SOUND = "desk_undock_sound";
/**
- * The number of milliseconds to delay when checking for data stalls during
- * aggressive detection. (screen on or suspected data stall)
+ * Whether to play a sound for dock events.
* @hide
*/
- public static final String DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS =
- "data_stall_alarm_aggressive_delay_in_ms";
+ public static final String DOCK_SOUNDS_ENABLED = "dock_sounds_enabled";
/**
- * The interval in milliseconds at which to check gprs registration
- * after the first registration mismatch of gprs and voice service,
- * to detect possible data network registration problems.
- *
+ * URI for the "device locked" (keyguard shown) sound.
* @hide
*/
- public static final String GPRS_REGISTER_CHECK_PERIOD_MS =
- "gprs_register_check_period_ms";
+ public static final String LOCK_SOUND = "lock_sound";
/**
- * The length of time in milli-seconds that automatic small adjustments to
- * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
+ * URI for the "device unlocked" sound.
* @hide
*/
- public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing";
+ public static final String UNLOCK_SOUND = "unlock_sound";
/**
- * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment
- * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
- * exceeded.
+ * URI for the low battery sound file.
* @hide
*/
- public static final String NITZ_UPDATE_DIFF = "nitz_update_diff";
+ public static final String LOW_BATTERY_SOUND = "low_battery_sound";
/**
- * The maximum reconnect delay for short network outages or when the network is suspended
- * due to phone use.
+ * Whether to play a sound for low-battery alerts.
* @hide
*/
- public static final String SYNC_MAX_RETRY_DELAY_IN_SECONDS =
- "sync_max_retry_delay_in_seconds";
+ public static final String POWER_SOUNDS_ENABLED = "power_sounds_enabled";
/**
- * The interval in milliseconds at which to check the number of SMS sent
- * out without asking for use permit, to limit the un-authorized SMS
- * usage.
- * @hide
+ * Whether we keep the device on while the device is plugged in.
+ * Supported values are:
+ * <ul>
+ * <li>{@code 0} to never stay on while plugged in</li>
+ * <li>{@link BatteryManager#BATTERY_PLUGGED_AC} to stay on for AC charger</li>
+ * <li>{@link BatteryManager#BATTERY_PLUGGED_USB} to stay on for USB charger</li>
+ * <li>{@link BatteryManager#BATTERY_PLUGGED_WIRELESS} to stay on for wireless charger</li>
+ * </ul>
+ * These values can be OR-ed together.
*/
- public static final String SMS_OUTGOING_CHECK_INTERVAL_MS =
- "sms_outgoing_check_interval_ms";
+ public static final String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
/**
- * The number of outgoing SMS sent without asking for user permit
- * (of {@link #SMS_OUTGOING_CHECK_INTERVAL_MS}
- * @hide
+ * Whether ADB is enabled.
*/
- public static final String SMS_OUTGOING_CHECK_MAX_COUNT =
- "sms_outgoing_check_max_count";
+ public static final String ADB_ENABLED = "adb_enabled";
/**
- * The global search provider chosen by the user (if multiple global
- * search providers are installed). This will be the provider returned
- * by {@link SearchManager#getGlobalSearchActivity()} if it's still
- * installed. This setting is stored as a flattened component name as
- * per {@link ComponentName#flattenToString()}.
- *
+ * Whether assisted GPS should be enabled or not.
* @hide
*/
- public static final String SEARCH_GLOBAL_SEARCH_ACTIVITY =
- "search_global_search_activity";
+ public static final String ASSISTED_GPS_ENABLED = "assisted_gps_enabled";
/**
- * The number of promoted sources in GlobalSearch.
- * @hide
+ * Whether bluetooth is enabled/disabled
+ * 0=disabled. 1=enabled.
*/
- public static final String SEARCH_NUM_PROMOTED_SOURCES = "search_num_promoted_sources";
+ public static final String BLUETOOTH_ON = "bluetooth_on";
+
/**
- * The maximum number of suggestions returned by GlobalSearch.
+ * CDMA Cell Broadcast SMS
+ * 0 = CDMA Cell Broadcast SMS disabled
+ * 1 = CDMA Cell Broadcast SMS enabled
* @hide
*/
- public static final String SEARCH_MAX_RESULTS_TO_DISPLAY = "search_max_results_to_display";
+ public static final String CDMA_CELL_BROADCAST_SMS =
+ "cdma_cell_broadcast_sms";
+
/**
- * The number of suggestions GlobalSearch will ask each non-web search source for.
+ * The CDMA roaming mode 0 = Home Networks, CDMA default
+ * 1 = Roaming on Affiliated networks
+ * 2 = Roaming on any networks
* @hide
*/
- public static final String SEARCH_MAX_RESULTS_PER_SOURCE = "search_max_results_per_source";
+ public static final String CDMA_ROAMING_MODE = "roaming_settings";
+
/**
- * The number of suggestions the GlobalSearch will ask the web search source for.
+ * The CDMA subscription mode 0 = RUIM/SIM (default)
+ * 1 = NV
* @hide
*/
- public static final String SEARCH_WEB_RESULTS_OVERRIDE_LIMIT =
- "search_web_results_override_limit";
+ public static final String CDMA_SUBSCRIPTION_MODE = "subscription_mode";
+
+ /** Inactivity timeout to track mobile data activity.
+ *
+ * If set to a positive integer, it indicates the inactivity timeout value in seconds to
+ * infer the data activity of mobile network. After a period of no activity on mobile
+ * networks with length specified by the timeout, an {@code ACTION_DATA_ACTIVITY_CHANGE}
+ * intent is fired to indicate a transition of network status from "active" to "idle". Any
+ * subsequent activity on mobile networks triggers the firing of {@code
+ * ACTION_DATA_ACTIVITY_CHANGE} intent indicating transition from "idle" to "active".
+ *
+ * Network activity refers to transmitting or receiving data on the network interfaces.
+ *
+ * Tracking is disabled if set to zero or negative value.
+ *
+ * @hide
+ */
+ public static final String DATA_ACTIVITY_TIMEOUT_MOBILE = "data_activity_timeout_mobile";
+
+ /** Timeout to tracking Wifi data activity. Same as {@code DATA_ACTIVITY_TIMEOUT_MOBILE}
+ * but for Wifi network.
+ * @hide
+ */
+ public static final String DATA_ACTIVITY_TIMEOUT_WIFI = "data_activity_timeout_wifi";
+
+ /**
+ * Whether or not data roaming is enabled. (0 = false, 1 = true)
+ */
+ public static final String DATA_ROAMING = "data_roaming";
+
+ /**
+ * Whether user has enabled development settings.
+ */
+ public static final String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled";
+
+ /**
+ * Whether the device has been provisioned (0 = false, 1 = true)
+ */
+ public static final String DEVICE_PROVISIONED = "device_provisioned";
+
+ /**
+ * The saved value for WindowManagerService.setForcedDisplayDensity().
+ * One integer in dpi. If unset, then use the real display density.
+ * @hide
+ */
+ public static final String DISPLAY_DENSITY_FORCED = "display_density_forced";
+
+ /**
+ * The saved value for WindowManagerService.setForcedDisplaySize().
+ * Two integers separated by a comma. If unset, then use the real display size.
+ * @hide
+ */
+ public static final String DISPLAY_SIZE_FORCED = "display_size_forced";
+
+ /**
+ * The maximum size, in bytes, of a download that the download manager will transfer over
+ * a non-wifi connection.
+ * @hide
+ */
+ public static final String DOWNLOAD_MAX_BYTES_OVER_MOBILE =
+ "download_manager_max_bytes_over_mobile";
+
+ /**
+ * The recommended maximum size, in bytes, of a download that the download manager should
+ * transfer over a non-wifi connection. Over this size, the use will be warned, but will
+ * have the option to start the download over the mobile connection anyway.
+ * @hide
+ */
+ public static final String DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE =
+ "download_manager_recommended_max_bytes_over_mobile";
+
+ /**
+ * Whether the package installer should allow installation of apps downloaded from
+ * sources other than Google Play.
+ *
+ * 1 = allow installing from other sources
+ * 0 = only allow installing from Google Play
+ */
+ public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
+
+ /**
+ * Whether mobile data connections are allowed by the user. See
+ * ConnectivityManager for more info.
+ * @hide
+ */
+ public static final String MOBILE_DATA = "mobile_data";
+
+ /** {@hide} */
+ public static final String NETSTATS_ENABLED = "netstats_enabled";
+ /** {@hide} */
+ public static final String NETSTATS_POLL_INTERVAL = "netstats_poll_interval";
+ /** {@hide} */
+ public static final String NETSTATS_TIME_CACHE_MAX_AGE = "netstats_time_cache_max_age";
+ /** {@hide} */
+ public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes";
+ /** {@hide} */
+ public static final String NETSTATS_SAMPLE_ENABLED = "netstats_sample_enabled";
+ /** {@hide} */
+ public static final String NETSTATS_REPORT_XT_OVER_DEV = "netstats_report_xt_over_dev";
+
+ /** {@hide} */
+ public static final String NETSTATS_DEV_BUCKET_DURATION = "netstats_dev_bucket_duration";
+ /** {@hide} */
+ public static final String NETSTATS_DEV_PERSIST_BYTES = "netstats_dev_persist_bytes";
+ /** {@hide} */
+ public static final String NETSTATS_DEV_ROTATE_AGE = "netstats_dev_rotate_age";
+ /** {@hide} */
+ public static final String NETSTATS_DEV_DELETE_AGE = "netstats_dev_delete_age";
+
+ /** {@hide} */
+ public static final String NETSTATS_UID_BUCKET_DURATION = "netstats_uid_bucket_duration";
+ /** {@hide} */
+ public static final String NETSTATS_UID_PERSIST_BYTES = "netstats_uid_persist_bytes";
+ /** {@hide} */
+ public static final String NETSTATS_UID_ROTATE_AGE = "netstats_uid_rotate_age";
+ /** {@hide} */
+ public static final String NETSTATS_UID_DELETE_AGE = "netstats_uid_delete_age";
+
+ /** {@hide} */
+ public static final String NETSTATS_UID_TAG_BUCKET_DURATION = "netstats_uid_tag_bucket_duration";
+ /** {@hide} */
+ public static final String NETSTATS_UID_TAG_PERSIST_BYTES = "netstats_uid_tag_persist_bytes";
+ /** {@hide} */
+ public static final String NETSTATS_UID_TAG_ROTATE_AGE = "netstats_uid_tag_rotate_age";
+ /** {@hide} */
+ public static final String NETSTATS_UID_TAG_DELETE_AGE = "netstats_uid_tag_delete_age";
+
+ /**
+ * User preference for which network(s) should be used. Only the
+ * connectivity service should touch this.
+ */
+ public static final String NETWORK_PREFERENCE = "network_preference";
+
+ /**
+ * If the NITZ_UPDATE_DIFF time is exceeded then an automatic adjustment
+ * to SystemClock will be allowed even if NITZ_UPDATE_SPACING has not been
+ * exceeded.
+ * @hide
+ */
+ public static final String NITZ_UPDATE_DIFF = "nitz_update_diff";
+
+ /**
+ * The length of time in milli-seconds that automatic small adjustments to
+ * SystemClock are ignored if NITZ_UPDATE_DIFF is not exceeded.
+ * @hide
+ */
+ public static final String NITZ_UPDATE_SPACING = "nitz_update_spacing";
+
+ /** Preferred NTP server. {@hide} */
+ public static final String NTP_SERVER = "ntp_server";
+ /** Timeout in milliseconds to wait for NTP server. {@hide} */
+ public static final String NTP_TIMEOUT = "ntp_timeout";
+
+ /**
+ * Whether the package manager should send package verification broadcasts for verifiers to
+ * review apps prior to installation.
+ * 1 = request apps to be verified prior to installation, if a verifier exists.
+ * 0 = do not verify apps before installation
+ * @hide
+ */
+ public static final String PACKAGE_VERIFIER_ENABLE = "package_verifier_enable";
+
+ /** Timeout for package verification.
+ * @hide */
+ public static final String PACKAGE_VERIFIER_TIMEOUT = "verifier_timeout";
+
+ /** Default response code for package verification.
+ * @hide */
+ public static final String PACKAGE_VERIFIER_DEFAULT_RESPONSE = "verifier_default_response";
+
+ /**
+ * Show package verification setting in the Settings app.
+ * 1 = show (default)
+ * 0 = hide
+ * @hide
+ */
+ public static final String PACKAGE_VERIFIER_SETTING_VISIBLE = "verifier_setting_visible";
+
+ /**
+ * Run package verificaiton on apps installed through ADB/ADT/USB
+ * 1 = perform package verification on ADB installs (default)
+ * 0 = bypass package verification on ADB installs
+ * @hide
+ */
+ public static final String PACKAGE_VERIFIER_INCLUDE_ADB = "verifier_verify_adb_installs";
+
+ /**
+ * The interval in milliseconds at which to check packet counts on the
+ * mobile data interface when screen is on, to detect possible data
+ * connection problems.
+ * @hide
+ */
+ public static final String PDP_WATCHDOG_POLL_INTERVAL_MS =
+ "pdp_watchdog_poll_interval_ms";
+
+ /**
+ * The interval in milliseconds at which to check packet counts on the
+ * mobile data interface when screen is off, to detect possible data
+ * connection problems.
+ * @hide
+ */
+ public static final String PDP_WATCHDOG_LONG_POLL_INTERVAL_MS =
+ "pdp_watchdog_long_poll_interval_ms";
+
+ /**
+ * The interval in milliseconds at which to check packet counts on the
+ * mobile data interface after {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT}
+ * outgoing packets has been reached without incoming packets.
+ * @hide
+ */
+ public static final String PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS =
+ "pdp_watchdog_error_poll_interval_ms";
+
+ /**
+ * The number of outgoing packets sent without seeing an incoming packet
+ * that triggers a countdown (of {@link #PDP_WATCHDOG_ERROR_POLL_COUNT}
+ * device is logged to the event log
+ * @hide
+ */
+ public static final String PDP_WATCHDOG_TRIGGER_PACKET_COUNT =
+ "pdp_watchdog_trigger_packet_count";
+
+ /**
+ * The number of polls to perform (at {@link #PDP_WATCHDOG_ERROR_POLL_INTERVAL_MS})
+ * after hitting {@link #PDP_WATCHDOG_TRIGGER_PACKET_COUNT} before
+ * attempting data connection recovery.
+ * @hide
+ */
+ public static final String PDP_WATCHDOG_ERROR_POLL_COUNT =
+ "pdp_watchdog_error_poll_count";
+
+ /**
+ * The number of failed PDP reset attempts before moving to something more
+ * drastic: re-registering to the network.
+ * @hide
+ */
+ public static final String PDP_WATCHDOG_MAX_PDP_RESET_FAIL_COUNT =
+ "pdp_watchdog_max_pdp_reset_fail_count";
+
+ /**
+ * A positive value indicates how often the SamplingProfiler
+ * should take snapshots. Zero value means SamplingProfiler
+ * is disabled.
+ *
+ * @hide
+ */
+ public static final String SAMPLING_PROFILER_MS = "sampling_profiler_ms";
+
+ /**
+ * URL to open browser on to allow user to manage a prepay account
+ * @hide
+ */
+ public static final String SETUP_PREPAID_DATA_SERVICE_URL =
+ "setup_prepaid_data_service_url";
+
+ /**
+ * URL to attempt a GET on to see if this is a prepay device
+ * @hide
+ */
+ public static final String SETUP_PREPAID_DETECTION_TARGET_URL =
+ "setup_prepaid_detection_target_url";
+
+ /**
+ * Host to check for a redirect to after an attempt to GET
+ * SETUP_PREPAID_DETECTION_TARGET_URL. (If we redirected there,
+ * this is a prepaid device with zero balance.)
+ * @hide
+ */
+ public static final String SETUP_PREPAID_DETECTION_REDIR_HOST =
+ "setup_prepaid_detection_redir_host";
+
+ /**
+ * The interval in milliseconds at which to check the number of SMS sent out without asking
+ * for use permit, to limit the un-authorized SMS usage.
+ *
+ * @hide
+ */
+ public static final String SMS_OUTGOING_CHECK_INTERVAL_MS =
+ "sms_outgoing_check_interval_ms";
+
+ /**
+ * The number of outgoing SMS sent without asking for user permit (of {@link
+ * #SMS_OUTGOING_CHECK_INTERVAL_MS}
+ *
+ * @hide
+ */
+ public static final String SMS_OUTGOING_CHECK_MAX_COUNT =
+ "sms_outgoing_check_max_count";
+
+ /**
+ * Used to disable SMS short code confirmation - defaults to true.
+ * True indcates we will do the check, etc. Set to false to disable.
+ * @see com.android.internal.telephony.SmsUsageMonitor
+ * @hide
+ */
+ public static final String SMS_SHORT_CODE_CONFIRMATION = "sms_short_code_confirmation";
+
/**
- * The number of milliseconds that GlobalSearch will wait for suggestions from
- * promoted sources before continuing with all other sources.
+ * Used to select which country we use to determine premium sms codes.
+ * One of com.android.internal.telephony.SMSDispatcher.PREMIUM_RULE_USE_SIM,
+ * com.android.internal.telephony.SMSDispatcher.PREMIUM_RULE_USE_NETWORK,
+ * or com.android.internal.telephony.SMSDispatcher.PREMIUM_RULE_USE_BOTH.
* @hide
*/
- public static final String SEARCH_PROMOTED_SOURCE_DEADLINE_MILLIS =
- "search_promoted_source_deadline_millis";
- /**
- * The number of milliseconds before GlobalSearch aborts search suggesiton queries.
+ public static final String SMS_SHORT_CODE_RULE = "sms_short_code_rule";
+
+ /**
+ * Used to disable Tethering on a device - defaults to true
+ * @hide
+ */
+ public static final String TETHER_SUPPORTED = "tether_supported";
+
+ /**
+ * Used to require DUN APN on the device or not - defaults to a build config value
+ * which defaults to false
+ * @hide
+ */
+ public static final String TETHER_DUN_REQUIRED = "tether_dun_required";
+
+ /**
+ * Used to hold a gservices-provisioned apn value for DUN. If set, or the
+ * corresponding build config values are set it will override the APN DB
+ * values.
+ * Consists of a comma seperated list of strings:
+ * "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type"
+ * note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN"
+ * @hide
+ */
+ public static final String TETHER_DUN_APN = "tether_dun_apn";
+
+ /**
+ * The bandwidth throttle polling freqency in seconds
+ * @hide
+ */
+ public static final String THROTTLE_POLLING_SEC = "throttle_polling_sec";
+
+ /**
+ * The bandwidth throttle threshold (long)
+ * @hide
+ */
+ public static final String THROTTLE_THRESHOLD_BYTES = "throttle_threshold_bytes";
+
+ /**
+ * The bandwidth throttle value (kbps)
+ * @hide
+ */
+ public static final String THROTTLE_VALUE_KBITSPS = "throttle_value_kbitsps";
+
+ /**
+ * The bandwidth throttle reset calendar day (1-28)
+ * @hide
+ */
+ public static final String THROTTLE_RESET_DAY = "throttle_reset_day";
+
+ /**
+ * The throttling notifications we should send
+ * @hide
+ */
+ public static final String THROTTLE_NOTIFICATION_TYPE = "throttle_notification_type";
+
+ /**
+ * Help URI for data throttling policy
+ * @hide
+ */
+ public static final String THROTTLE_HELP_URI = "throttle_help_uri";
+
+ /**
+ * The length of time in Sec that we allow our notion of NTP time
+ * to be cached before we refresh it
+ * @hide
+ */
+ public static final String THROTTLE_MAX_NTP_CACHE_AGE_SEC =
+ "throttle_max_ntp_cache_age_sec";
+
+ /**
+ * USB Mass Storage Enabled
+ */
+ public static final String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
+
+ /**
+ * If this setting is set (to anything), then all references
+ * to Gmail on the device must change to Google Mail.
+ */
+ public static final String USE_GOOGLE_MAIL = "use_google_mail";
+
+ /** Autofill server address (Used in WebView/browser).
+ * {@hide} */
+ public static final String WEB_AUTOFILL_QUERY_URL =
+ "web_autofill_query_url";
+
+ /**
+ * Whether Wifi display is enabled/disabled
+ * 0=disabled. 1=enabled.
+ * @hide
+ */
+ public static final String WIFI_DISPLAY_ON = "wifi_display_on";
+
+ /**
+ * Whether to notify the user of open networks.
+ * <p>
+ * If not connected and the scan results have an open network, we will
+ * put this notification up. If we attempt to connect to a network or
+ * the open network(s) disappear, we remove the notification. When we
+ * show the notification, we will not show it again for
+ * {@link android.provider.Settings.Secure#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time.
+ */
+ public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
+ "wifi_networks_available_notification_on";
+ /**
+ * {@hide}
+ */
+ public static final String WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON =
+ "wimax_networks_available_notification_on";
+
+ /**
+ * Delay (in seconds) before repeating the Wi-Fi networks available notification.
+ * Connecting to a network will reset the timer.
+ */
+ public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
+ "wifi_networks_available_repeat_delay";
+
+ /**
+ * 802.11 country code in ISO 3166 format
+ * @hide
+ */
+ public static final String WIFI_COUNTRY_CODE = "wifi_country_code";
+
+ /**
+ * The interval in milliseconds to issue wake up scans when wifi needs
+ * to connect. This is necessary to connect to an access point when
+ * device is on the move and the screen is off.
+ * @hide
+ */
+ public static final String WIFI_FRAMEWORK_SCAN_INTERVAL_MS =
+ "wifi_framework_scan_interval_ms";
+
+ /**
+ * The interval in milliseconds after which Wi-Fi is considered idle.
+ * When idle, it is possible for the device to be switched from Wi-Fi to
+ * the mobile data network.
+ * @hide
+ */
+ public static final String WIFI_IDLE_MS = "wifi_idle_ms";
+
+ /**
+ * When the number of open networks exceeds this number, the
+ * least-recently-used excess networks will be removed.
+ */
+ public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
+
+ /**
+ * Whether the Wi-Fi should be on. Only the Wi-Fi service should touch this.
+ */
+ public static final String WIFI_ON = "wifi_on";
+
+ /**
+ * Used to save the Wifi_ON state prior to tethering.
+ * This state will be checked to restore Wifi after
+ * the user turns off tethering.
+ *
+ * @hide
+ */
+ public static final String WIFI_SAVED_STATE = "wifi_saved_state";
+
+ /**
+ * The interval in milliseconds to scan as used by the wifi supplicant
+ * @hide
+ */
+ public static final String WIFI_SUPPLICANT_SCAN_INTERVAL_MS =
+ "wifi_supplicant_scan_interval_ms";
+
+ /**
+ * The interval in milliseconds to scan at supplicant when p2p is connected
+ * @hide
+ */
+ public static final String WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS =
+ "wifi_scan_interval_p2p_connected_ms";
+
+ /**
+ * Whether the Wi-Fi watchdog is enabled.
+ */
+ public static final String WIFI_WATCHDOG_ON = "wifi_watchdog_on";
+
+ /**
+ * Setting to turn off poor network avoidance on Wi-Fi. Feature is enabled by default and
+ * the setting needs to be set to 0 to disable it.
+ * @hide
+ */
+ public static final String WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED =
+ "wifi_watchdog_poor_network_test_enabled";
+
+ /**
+ * Setting to turn on suspend optimizations at screen off on Wi-Fi. Enabled by default and
+ * needs to be set to 0 to disable it.
+ * @hide
+ */
+ public static final String WIFI_SUSPEND_OPTIMIZATIONS_ENABLED =
+ "wifi_suspend_optimizations_enabled";
+
+ /**
+ * The maximum number of times we will retry a connection to an access
+ * point for which we have failed in acquiring an IP address from DHCP.
+ * A value of N means that we will make N+1 connection attempts in all.
+ */
+ public static final String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
+
+ /**
+ * Maximum amount of time in milliseconds to hold a wakelock while waiting for mobile
+ * data connectivity to be established after a disconnect from Wi-Fi.
+ */
+ public static final String WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS =
+ "wifi_mobile_data_transition_wakelock_timeout_ms";
+
+ /**
+ * The operational wifi frequency band
+ * Set to one of {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO},
+ * {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ} or
+ * {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ}
+ *
+ * @hide
+ */
+ public static final String WIFI_FREQUENCY_BAND = "wifi_frequency_band";
+
+ /**
+ * The Wi-Fi peer-to-peer device name
+ * @hide
+ */
+ public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
+
+ /**
+ * The number of milliseconds to delay when checking for data stalls during
+ * non-aggressive detection. (screen is turned off.)
+ * @hide
+ */
+ public static final String DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS =
+ "data_stall_alarm_non_aggressive_delay_in_ms";
+
+ /**
+ * The number of milliseconds to delay when checking for data stalls during
+ * aggressive detection. (screen on or suspected data stall)
+ * @hide
+ */
+ public static final String DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS =
+ "data_stall_alarm_aggressive_delay_in_ms";
+
+ /**
+ * The interval in milliseconds at which to check gprs registration
+ * after the first registration mismatch of gprs and voice service,
+ * to detect possible data network registration problems.
+ *
+ * @hide
+ */
+ public static final String GPRS_REGISTER_CHECK_PERIOD_MS =
+ "gprs_register_check_period_ms";
+
+ /**
+ * Nonzero causes Log.wtf() to crash.
+ * @hide
+ */
+ public static final String WTF_IS_FATAL = "wtf_is_fatal";
+
+ /**
+ * Ringer mode. This is used internally, changing this value will not
+ * change the ringer mode. See AudioManager.
+ */
+ public static final String MODE_RINGER = "mode_ringer";
+
+ /**
+ * Overlay display devices setting.
+ * The associated value is a specially formatted string that describes the
+ * size and density of simulated secondary display devices.
+ * <p>
+ * Format: {width}x{height}/{dpi};...
+ * </p><p>
+ * Example:
+ * <ul>
+ * <li><code>1280x720/213</code>: make one overlay that is 1280x720 at 213dpi.</li>
+ * <li><code>1920x1080/320;1280x720/213</code>: make two overlays, the first
+ * at 1080p and the second at 720p.</li>
+ * <li>If the value is empty, then no overlay display devices are created.</li>
+ * </ul></p>
+ *
+ * @hide
+ */
+ public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
+
+ /**
+ * Threshold values for the duration and level of a discharge cycle,
+ * under which we log discharge cycle info.
+ *
* @hide
*/
- public static final String SEARCH_SOURCE_TIMEOUT_MILLIS = "search_source_timeout_millis";
+ public static final String
+ BATTERY_DISCHARGE_DURATION_THRESHOLD = "battery_discharge_duration_threshold";
+
+ /** @hide */
+ public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold";
+
/**
- * The maximum number of milliseconds that GlobalSearch shows the previous results
- * after receiving a new query.
+ * Flag for allowing ActivityManagerService to send ACTION_APP_ERROR
+ * intents on application crashes and ANRs. If this is disabled, the
+ * crash/ANR dialog will never display the "Report" button.
+ * <p>
+ * Type: int (0 = disallow, 1 = allow)
+ *
* @hide
*/
- public static final String SEARCH_PREFILL_MILLIS = "search_prefill_millis";
+ public static final String SEND_ACTION_APP_ERROR = "send_action_app_error";
+
/**
- * The maximum age of log data used for shortcuts in GlobalSearch.
+ * Maximum age of entries kept by {@link DropBoxManager}.
+ *
* @hide
*/
- public static final String SEARCH_MAX_STAT_AGE_MILLIS = "search_max_stat_age_millis";
+ public static final String DROPBOX_AGE_SECONDS = "dropbox_age_seconds";
+
/**
- * The maximum age of log data used for source ranking in GlobalSearch.
+ * Maximum number of entry files which {@link DropBoxManager} will keep
+ * around.
+ *
* @hide
*/
- public static final String SEARCH_MAX_SOURCE_EVENT_AGE_MILLIS =
- "search_max_source_event_age_millis";
+ public static final String DROPBOX_MAX_FILES = "dropbox_max_files";
+
/**
- * The minimum number of impressions needed to rank a source in GlobalSearch.
+ * Maximum amount of disk space used by {@link DropBoxManager} no matter
+ * what.
+ *
* @hide
*/
- public static final String SEARCH_MIN_IMPRESSIONS_FOR_SOURCE_RANKING =
- "search_min_impressions_for_source_ranking";
+ public static final String DROPBOX_QUOTA_KB = "dropbox_quota_kb";
+
/**
- * The minimum number of clicks needed to rank a source in GlobalSearch.
+ * Percent of free disk (excluding reserve) which {@link DropBoxManager}
+ * will use.
+ *
* @hide
*/
- public static final String SEARCH_MIN_CLICKS_FOR_SOURCE_RANKING =
- "search_min_clicks_for_source_ranking";
+ public static final String DROPBOX_QUOTA_PERCENT = "dropbox_quota_percent";
+
/**
- * The maximum number of shortcuts shown by GlobalSearch.
+ * Percent of total disk which {@link DropBoxManager} will never dip
+ * into.
+ *
* @hide
*/
- public static final String SEARCH_MAX_SHORTCUTS_RETURNED = "search_max_shortcuts_returned";
+ public static final String DROPBOX_RESERVE_PERCENT = "dropbox_reserve_percent";
+
/**
- * The size of the core thread pool for suggestion queries in GlobalSearch.
+ * Prefix for per-tag dropbox disable/enable settings.
+ *
* @hide
*/
- public static final String SEARCH_QUERY_THREAD_CORE_POOL_SIZE =
- "search_query_thread_core_pool_size";
+ public static final String DROPBOX_TAG_PREFIX = "dropbox:";
+
/**
- * The maximum size of the thread pool for suggestion queries in GlobalSearch.
+ * Lines of logcat to include with system crash/ANR/etc. reports, as a
+ * prefix of the dropbox tag of the report type. For example,
+ * "logcat_for_system_server_anr" controls the lines of logcat captured
+ * with system server ANR reports. 0 to disable.
+ *
* @hide
*/
- public static final String SEARCH_QUERY_THREAD_MAX_POOL_SIZE =
- "search_query_thread_max_pool_size";
+ public static final String ERROR_LOGCAT_PREFIX = "logcat_for_";
+
/**
- * The size of the core thread pool for shortcut refreshing in GlobalSearch.
+ * The interval in minutes after which the amount of free storage left
+ * on the device is logged to the event log
+ *
* @hide
*/
- public static final String SEARCH_SHORTCUT_REFRESH_CORE_POOL_SIZE =
- "search_shortcut_refresh_core_pool_size";
+ public static final String SYS_FREE_STORAGE_LOG_INTERVAL = "sys_free_storage_log_interval";
+
/**
- * The maximum size of the thread pool for shortcut refreshing in GlobalSearch.
+ * Threshold for the amount of change in disk free space required to
+ * report the amount of free space. Used to prevent spamming the logs
+ * when the disk free space isn't changing frequently.
+ *
* @hide
*/
- public static final String SEARCH_SHORTCUT_REFRESH_MAX_POOL_SIZE =
- "search_shortcut_refresh_max_pool_size";
+ public static final String
+ DISK_FREE_CHANGE_REPORTING_THRESHOLD = "disk_free_change_reporting_threshold";
+
/**
- * The maximun time that excess threads in the GlobalSeach thread pools will
- * wait before terminating.
+ * Minimum percentage of free storage on the device that is used to
+ * determine if the device is running low on storage. The default is 10.
+ * <p>
+ * Say this value is set to 10, the device is considered running low on
+ * storage if 90% or more of the device storage is filled up.
+ *
* @hide
*/
- public static final String SEARCH_THREAD_KEEPALIVE_SECONDS =
- "search_thread_keepalive_seconds";
+ public static final String
+ SYS_STORAGE_THRESHOLD_PERCENTAGE = "sys_storage_threshold_percentage";
+
/**
- * The maximum number of concurrent suggestion queries to each source.
+ * Maximum byte size of the low storage threshold. This is to ensure
+ * that {@link #SYS_STORAGE_THRESHOLD_PERCENTAGE} does not result in an
+ * overly large threshold for large storage devices. Currently this must
+ * be less than 2GB. This default is 500MB.
+ *
* @hide
*/
- public static final String SEARCH_PER_SOURCE_CONCURRENT_QUERY_LIMIT =
- "search_per_source_concurrent_query_limit";
+ public static final String
+ SYS_STORAGE_THRESHOLD_MAX_BYTES = "sys_storage_threshold_max_bytes";
/**
- * Whether or not alert sounds are played on MountService events. (0 = false, 1 = true)
+ * Minimum bytes of free storage on the device before the data partition
+ * is considered full. By default, 1 MB is reserved to avoid system-wide
+ * SQLite disk full exceptions.
+ *
* @hide
*/
- public static final String MOUNT_PLAY_NOTIFICATION_SND = "mount_play_not_snd";
+ public static final String
+ SYS_STORAGE_FULL_THRESHOLD_BYTES = "sys_storage_full_threshold_bytes";
/**
- * Whether or not UMS auto-starts on UMS host detection. (0 = false, 1 = true)
+ * The maximum reconnect delay for short network outages or when the
+ * network is suspended due to phone use.
+ *
* @hide
*/
- public static final String MOUNT_UMS_AUTOSTART = "mount_ums_autostart";
+ public static final String
+ SYNC_MAX_RETRY_DELAY_IN_SECONDS = "sync_max_retry_delay_in_seconds";
/**
- * Whether or not a notification is displayed on UMS host detection. (0 = false, 1 = true)
+ * The number of milliseconds to delay before sending out
+ * {@link ConnectivityManager#CONNECTIVITY_ACTION} broadcasts.
+ *
* @hide
*/
- public static final String MOUNT_UMS_PROMPT = "mount_ums_prompt";
+ public static final String CONNECTIVITY_CHANGE_DELAY = "connectivity_change_delay";
/**
- * Whether or not a notification is displayed while UMS is enabled. (0 = false, 1 = true)
+ * Setting to turn off captive portal detection. Feature is enabled by
+ * default and the setting needs to be set to 0 to disable it.
+ *
* @hide
*/
- public static final String MOUNT_UMS_NOTIFY_ENABLED = "mount_ums_notify_enabled";
+ public static final String
+ CAPTIVE_PORTAL_DETECTION_ENABLED = "captive_portal_detection_enabled";
/**
- * If nonzero, ANRs in invisible background processes bring up a dialog.
- * Otherwise, the process will be silently killed.
+ * The server used for captive portal detection upon a new conection. A
+ * 204 response code from the server is used for validation.
+ *
* @hide
*/
- public static final String ANR_SHOW_BACKGROUND = "anr_show_background";
+ public static final String CAPTIVE_PORTAL_SERVER = "captive_portal_server";
/**
- * The {@link ComponentName} string of the service to be used as the voice recognition
- * service.
+ * Whether network service discovery is enabled.
*
* @hide
*/
- public static final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service";
-
+ public static final String NSD_ON = "nsd_on";
/**
- * The {@link ComponentName} string of the selected spell checker service which is
- * one of the services managed by the text service manager.
+ * Let user pick default install location.
*
* @hide
*/
- public static final String SELECTED_SPELL_CHECKER = "selected_spell_checker";
+ public static final String SET_INSTALL_LOCATION = "set_install_location";
/**
- * The {@link ComponentName} string of the selected subtype of the selected spell checker
- * service which is one of the services managed by the text service manager.
- *
+ * Default install location value.
+ * 0 = auto, let system decide
+ * 1 = internal
+ * 2 = sdcard
* @hide
*/
- public static final String SELECTED_SPELL_CHECKER_SUBTYPE =
- "selected_spell_checker_subtype";
+ public static final String DEFAULT_INSTALL_LOCATION = "default_install_location";
/**
- * The {@link ComponentName} string whether spell checker is enabled or not.
+ * ms during which to consume extra events related to Inet connection
+ * condition after a transtion to fully-connected
*
* @hide
*/
- public static final String SPELL_CHECKER_ENABLED = "spell_checker_enabled";
+ public static final String
+ INET_CONDITION_DEBOUNCE_UP_DELAY = "inet_condition_debounce_up_delay";
/**
- * What happens when the user presses the Power button while in-call
- * and the screen is on.<br/>
- * <b>Values:</b><br/>
- * 1 - The Power button turns off the screen and locks the device. (Default behavior)<br/>
- * 2 - The Power button hangs up the current call.<br/>
+ * ms during which to consume extra events related to Inet connection
+ * condtion after a transtion to partly-connected
*
* @hide
*/
- public static final String INCALL_POWER_BUTTON_BEHAVIOR = "incall_power_button_behavior";
+ public static final String
+ INET_CONDITION_DEBOUNCE_DOWN_DELAY = "inet_condition_debounce_down_delay";
+
+ /** {@hide} */
+ public static final String
+ READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT = "read_external_storage_enforced_default";
/**
- * INCALL_POWER_BUTTON_BEHAVIOR value for "turn off screen".
- * @hide
+ * Host name and port for global http proxy. Uses ':' seperator for
+ * between host and port.
*/
- public static final int INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF = 0x1;
+ public static final String HTTP_PROXY = "http_proxy";
/**
- * INCALL_POWER_BUTTON_BEHAVIOR value for "hang up".
+ * Host name for global http proxy. Set via ConnectivityManager.
+ *
* @hide
*/
- public static final int INCALL_POWER_BUTTON_BEHAVIOR_HANGUP = 0x2;
+ public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host";
/**
- * INCALL_POWER_BUTTON_BEHAVIOR default value.
+ * Integer host port for global http proxy. Set via ConnectivityManager.
+ *
* @hide
*/
- public static final int INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT =
- INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF;
+ public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port";
/**
- * The current night mode that has been selected by the user. Owned
- * and controlled by UiModeManagerService. Constants are as per
- * UiModeManager.
+ * Exclusion list for global proxy. This string contains a list of
+ * comma-separated domains where the global proxy does not apply.
+ * Domains should be listed in a comma- separated list. Example of
+ * acceptable formats: ".domain1.com,my.domain2.com" Use
+ * ConnectivityManager to set/get.
+ *
* @hide
*/
- public static final String UI_NIGHT_MODE = "ui_night_mode";
+ public static final String
+ GLOBAL_HTTP_PROXY_EXCLUSION_LIST = "global_http_proxy_exclusion_list";
/**
- * Let user pick default install location.
+ * Enables the UI setting to allow the user to specify the global HTTP
+ * proxy and associated exclusion list.
+ *
* @hide
*/
- public static final String SET_INSTALL_LOCATION = "set_install_location";
+ public static final String SET_GLOBAL_HTTP_PROXY = "set_global_http_proxy";
/**
- * Default install location value.
- * 0 = auto, let system decide
- * 1 = internal
- * 2 = sdcard
+ * Setting for default DNS in case nobody suggests one
+ *
* @hide
*/
- public static final String DEFAULT_INSTALL_LOCATION = "default_install_location";
+ public static final String DEFAULT_DNS_SERVER = "default_dns_server";
+
+ /** {@hide} */
+ public static final String
+ BLUETOOTH_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_";
+ /** {@hide} */
+ public static final String
+ BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX = "bluetooth_a2dp_sink_priority_";
+ /** {@hide} */
+ public static final String
+ BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX = "bluetooth_input_device_priority_";
/**
- * The bandwidth throttle polling freqency in seconds
+ * Get the key that retrieves a bluetooth headset's priority.
* @hide
*/
- public static final String THROTTLE_POLLING_SEC = "throttle_polling_sec";
+ public static final String getBluetoothHeadsetPriorityKey(String address) {
+ return BLUETOOTH_HEADSET_PRIORITY_PREFIX + address.toUpperCase();
+ }
/**
- * The bandwidth throttle threshold (long)
+ * Get the key that retrieves a bluetooth a2dp sink's priority.
* @hide
*/
- public static final String THROTTLE_THRESHOLD_BYTES = "throttle_threshold_bytes";
+ public static final String getBluetoothA2dpSinkPriorityKey(String address) {
+ return BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX + address.toUpperCase();
+ }
/**
- * The bandwidth throttle value (kbps)
+ * Get the key that retrieves a bluetooth Input Device's priority.
* @hide
*/
- public static final String THROTTLE_VALUE_KBITSPS = "throttle_value_kbitsps";
+ public static final String getBluetoothInputDevicePriorityKey(String address) {
+ return BLUETOOTH_INPUT_DEVICE_PRIORITY_PREFIX + address.toUpperCase();
+ }
/**
- * The bandwidth throttle reset calendar day (1-28)
- * @hide
+ * Scaling factor for normal window animations. Setting to 0 will
+ * disable window animations.
*/
- public static final String THROTTLE_RESET_DAY = "throttle_reset_day";
+ public static final String WINDOW_ANIMATION_SCALE = "window_animation_scale";
/**
- * The throttling notifications we should send
- * @hide
+ * Scaling factor for activity transition animations. Setting to 0 will
+ * disable window animations.
*/
- public static final String THROTTLE_NOTIFICATION_TYPE = "throttle_notification_type";
+ public static final String TRANSITION_ANIMATION_SCALE = "transition_animation_scale";
/**
- * Help URI for data throttling policy
- * @hide
+ * Scaling factor for Animator-based animations. This affects both the
+ * start delay and duration of all such animations. Setting to 0 will
+ * cause animations to end immediately. The default value is 1.
*/
- public static final String THROTTLE_HELP_URI = "throttle_help_uri";
+ public static final String ANIMATOR_DURATION_SCALE = "animator_duration_scale";
/**
- * The length of time in Sec that we allow our notion of NTP time
- * to be cached before we refresh it
+ * Scaling factor for normal window animations. Setting to 0 will
+ * disable window animations.
+ *
* @hide
*/
- public static final String THROTTLE_MAX_NTP_CACHE_AGE_SEC =
- "throttle_max_ntp_cache_age_sec";
+ public static final String FANCY_IME_ANIMATIONS = "fancy_ime_animations";
/**
- * The maximum size, in bytes, of a download that the download manager will transfer over
- * a non-wifi connection.
+ * If 0, the compatibility mode is off for all applications.
+ * If 1, older applications run under compatibility mode.
+ * TODO: remove this settings before code freeze (bug/1907571)
* @hide
*/
- public static final String DOWNLOAD_MAX_BYTES_OVER_MOBILE =
- "download_manager_max_bytes_over_mobile";
+ public static final String COMPATIBILITY_MODE = "compatibility_mode";
/**
- * The recommended maximum size, in bytes, of a download that the download manager should
- * transfer over a non-wifi connection. Over this size, the use will be warned, but will
- * have the option to start the download over the mobile connection anyway.
+ * CDMA only settings
+ * Emergency Tone 0 = Off
+ * 1 = Alert
+ * 2 = Vibrate
* @hide
*/
- public static final String DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE =
- "download_manager_recommended_max_bytes_over_mobile";
+ public static final String EMERGENCY_TONE = "emergency_tone";
/**
- * ms during which to consume extra events related to Inet connection condition
- * after a transtion to fully-connected
+ * CDMA only settings
+ * Whether the auto retry is enabled. The value is
+ * boolean (1 or 0).
* @hide
*/
- public static final String INET_CONDITION_DEBOUNCE_UP_DELAY =
- "inet_condition_debounce_up_delay";
+ public static final String CALL_AUTO_RETRY = "call_auto_retry";
/**
- * ms during which to consume extra events related to Inet connection condtion
- * after a transtion to partly-connected
+ * The preferred network mode 7 = Global
+ * 6 = EvDo only
+ * 5 = CDMA w/o EvDo
+ * 4 = CDMA / EvDo auto
+ * 3 = GSM / WCDMA auto
+ * 2 = WCDMA only
+ * 1 = GSM only
+ * 0 = GSM / WCDMA preferred
* @hide
*/
- public static final String INET_CONDITION_DEBOUNCE_DOWN_DELAY =
- "inet_condition_debounce_down_delay";
+ public static final String PREFERRED_NETWORK_MODE =
+ "preferred_network_mode";
/**
- * URL to open browser on to allow user to manage a prepay account
+ * The cdma subscription 0 = Subscription from RUIM, when available
+ * 1 = Subscription from NV
* @hide
*/
- public static final String SETUP_PREPAID_DATA_SERVICE_URL =
- "setup_prepaid_data_service_url";
+ public static final String PREFERRED_CDMA_SUBSCRIPTION =
+ "preferred_cdma_subscription";
/**
- * URL to attempt a GET on to see if this is a prepay device
- * @hide
+ * Name of an application package to be debugged.
*/
- public static final String SETUP_PREPAID_DETECTION_TARGET_URL =
- "setup_prepaid_detection_target_url";
+ public static final String DEBUG_APP = "debug_app";
/**
- * Host to check for a redirect to after an attempt to GET
- * SETUP_PREPAID_DETECTION_TARGET_URL. (If we redirected there,
- * this is a prepaid device with zero balance.)
- * @hide
+ * If 1, when launching DEBUG_APP it will wait for the debugger before
+ * starting user code. If 0, it will run normally.
*/
- public static final String SETUP_PREPAID_DETECTION_REDIR_HOST =
- "setup_prepaid_detection_redir_host";
+ public static final String WAIT_FOR_DEBUGGER = "wait_for_debugger";
/**
- * Whether the screensaver is enabled.
- * @hide
+ * Control whether the process CPU usage meter should be shown.
*/
- public static final String SCREENSAVER_ENABLED = "screensaver_enabled";
+ public static final String SHOW_PROCESSES = "show_processes";
/**
- * The user's chosen screensaver component.
- *
- * This component will be launched by the PhoneWindowManager after a timeout when not on
- * battery, or upon dock insertion (if SCREENSAVER_ACTIVATE_ON_DOCK is set to 1).
- * @hide
+ * If 1, the activity manager will aggressively finish activities and
+ * processes as soon as they are no longer needed. If 0, the normal
+ * extended lifetime is used.
*/
- public static final String SCREENSAVER_COMPONENT = "screensaver_component";
+ public static final String ALWAYS_FINISH_ACTIVITIES =
+ "always_finish_activities";
+
+ // Populated lazily, guarded by class object:
+ private static NameValueCache sNameValueCache = new NameValueCache(
+ SYS_PROP_SETTING_VERSION,
+ CONTENT_URI,
+ CALL_METHOD_GET_GLOBAL,
+ CALL_METHOD_PUT_GLOBAL);
/**
- * Whether the screensaver should be automatically launched when the device is inserted
- * into a (desk) dock.
- * @hide
+ * Look up a name in the database.
+ * @param resolver to access the database with
+ * @param name to look up in the table
+ * @return the corresponding value, or null if not present
*/
- public static final String SCREENSAVER_ACTIVATE_ON_DOCK = "screensaver_activate_on_dock";
-
- /** {@hide} */
- public static final String NETSTATS_ENABLED = "netstats_enabled";
- /** {@hide} */
- public static final String NETSTATS_POLL_INTERVAL = "netstats_poll_interval";
- /** {@hide} */
- public static final String NETSTATS_TIME_CACHE_MAX_AGE = "netstats_time_cache_max_age";
- /** {@hide} */
- public static final String NETSTATS_GLOBAL_ALERT_BYTES = "netstats_global_alert_bytes";
- /** {@hide} */
- public static final String NETSTATS_SAMPLE_ENABLED = "netstats_sample_enabled";
- /** {@hide} */
- public static final String NETSTATS_REPORT_XT_OVER_DEV = "netstats_report_xt_over_dev";
+ public static String getString(ContentResolver resolver, String name) {
+ return getStringForUser(resolver, name, UserHandle.myUserId());
+ }
- /** {@hide} */
- public static final String NETSTATS_DEV_BUCKET_DURATION = "netstats_dev_bucket_duration";
- /** {@hide} */
- public static final String NETSTATS_DEV_PERSIST_BYTES = "netstats_dev_persist_bytes";
- /** {@hide} */
- public static final String NETSTATS_DEV_ROTATE_AGE = "netstats_dev_rotate_age";
- /** {@hide} */
- public static final String NETSTATS_DEV_DELETE_AGE = "netstats_dev_delete_age";
+ /** @hide */
+ public static String getStringForUser(ContentResolver resolver, String name,
+ int userHandle) {
+ return sNameValueCache.getStringForUser(resolver, name, userHandle);
+ }
- /** {@hide} */
- public static final String NETSTATS_UID_BUCKET_DURATION = "netstats_uid_bucket_duration";
- /** {@hide} */
- public static final String NETSTATS_UID_PERSIST_BYTES = "netstats_uid_persist_bytes";
- /** {@hide} */
- public static final String NETSTATS_UID_ROTATE_AGE = "netstats_uid_rotate_age";
- /** {@hide} */
- public static final String NETSTATS_UID_DELETE_AGE = "netstats_uid_delete_age";
+ /**
+ * Store a name/value pair into the database.
+ * @param resolver to access the database with
+ * @param name to store
+ * @param value to associate with the name
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putString(ContentResolver resolver,
+ String name, String value) {
+ return putStringForUser(resolver, name, value, UserHandle.myUserId());
+ }
- /** {@hide} */
- public static final String NETSTATS_UID_TAG_BUCKET_DURATION = "netstats_uid_tag_bucket_duration";
- /** {@hide} */
- public static final String NETSTATS_UID_TAG_PERSIST_BYTES = "netstats_uid_tag_persist_bytes";
- /** {@hide} */
- public static final String NETSTATS_UID_TAG_ROTATE_AGE = "netstats_uid_tag_rotate_age";
- /** {@hide} */
- public static final String NETSTATS_UID_TAG_DELETE_AGE = "netstats_uid_tag_delete_age";
+ /** @hide */
+ public static boolean putStringForUser(ContentResolver resolver,
+ String name, String value, int userHandle) {
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Global.putString(name=" + name + ", value=" + value
+ + " for " + userHandle);
+ }
+ return sNameValueCache.putStringForUser(resolver, name, value, userHandle);
+ }
- /** Preferred NTP server. {@hide} */
- public static final String NTP_SERVER = "ntp_server";
- /** Timeout in milliseconds to wait for NTP server. {@hide} */
- public static final String NTP_TIMEOUT = "ntp_timeout";
+ /**
+ * Construct the content URI for a particular name/value pair,
+ * useful for monitoring changes with a ContentObserver.
+ * @param name to look up in the table
+ * @return the corresponding content URI, or null if not present
+ */
+ public static Uri getUriFor(String name) {
+ return getUriFor(CONTENT_URI, name);
+ }
- /** Autofill server address (Used in WebView/browser). {@hide} */
- public static final String WEB_AUTOFILL_QUERY_URL =
- "web_autofill_query_url";
+ /**
+ * Convenience function for retrieving a single secure settings value
+ * as an integer. Note that internally setting values are always
+ * stored as strings; this function converts the string to an integer
+ * for you. The default value will be returned if the setting is
+ * not defined or not an integer.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid integer.
+ */
+ public static int getInt(ContentResolver cr, String name, int def) {
+ String v = getString(cr, name);
+ try {
+ return v != null ? Integer.parseInt(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
- /** Whether package verification is enabled. {@hide} */
- public static final String PACKAGE_VERIFIER_ENABLE = "verifier_enable";
+ /**
+ * Convenience function for retrieving a single secure settings value
+ * as an integer. Note that internally setting values are always
+ * stored as strings; this function converts the string to an integer
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link SettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws SettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not an integer.
+ *
+ * @return The setting's current value.
+ */
+ public static int getInt(ContentResolver cr, String name)
+ throws SettingNotFoundException {
+ String v = getString(cr, name);
+ try {
+ return Integer.parseInt(v);
+ } catch (NumberFormatException e) {
+ throw new SettingNotFoundException(name);
+ }
+ }
- /** Timeout for package verification. {@hide} */
- public static final String PACKAGE_VERIFIER_TIMEOUT = "verifier_timeout";
+ /**
+ * Convenience function for updating a single settings value as an
+ * integer. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
+ */
+ public static boolean putInt(ContentResolver cr, String name, int value) {
+ return putString(cr, name, Integer.toString(value));
+ }
- /** {@hide} */
- public static final String
- READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT = "read_external_storage_enforced_default";
+ /**
+ * Convenience function for retrieving a single secure settings value
+ * as a {@code long}. Note that internally setting values are always
+ * stored as strings; this function converts the string to a {@code long}
+ * for you. The default value will be returned if the setting is
+ * not defined or not a {@code long}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
+ *
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid {@code long}.
+ */
+ public static long getLong(ContentResolver cr, String name, long def) {
+ String valString = getString(cr, name);
+ long value;
+ try {
+ value = valString != null ? Long.parseLong(valString) : def;
+ } catch (NumberFormatException e) {
+ value = def;
+ }
+ return value;
+ }
/**
- * Duration in milliseconds before pre-authorized URIs for the contacts
- * provider should expire.
- * @hide
+ * Convenience function for retrieving a single secure settings value
+ * as a {@code long}. Note that internally setting values are always
+ * stored as strings; this function converts the string to a {@code long}
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link SettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @return The setting's current value.
+ * @throws SettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not an integer.
*/
- public static final String CONTACTS_PREAUTH_URI_EXPIRATION =
- "contacts_preauth_uri_expiration";
+ public static long getLong(ContentResolver cr, String name)
+ throws SettingNotFoundException {
+ String valString = getString(cr, name);
+ try {
+ return Long.parseLong(valString);
+ } catch (NumberFormatException e) {
+ throw new SettingNotFoundException(name);
+ }
+ }
/**
- * Prefix for SMS short code regex patterns (country code is appended).
- * @see com.android.internal.telephony.SmsUsageMonitor
- * @hide
+ * Convenience function for updating a secure settings value as a long
+ * integer. This will either create a new entry in the table if the
+ * given name does not exist, or modify the value of the existing row
+ * with that name. Note that internally setting values are always
+ * stored as strings, so this function converts the given value to a
+ * string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
*/
- public static final String SMS_SHORT_CODES_PREFIX = "sms_short_codes_";
+ public static boolean putLong(ContentResolver cr, String name, long value) {
+ return putString(cr, name, Long.toString(value));
+ }
/**
- * This are the settings to be backed up.
+ * Convenience function for retrieving a single secure settings value
+ * as a floating point number. Note that internally setting values are
+ * always stored as strings; this function converts the string to an
+ * float for you. The default value will be returned if the setting
+ * is not defined or not a valid float.
*
- * NOTE: Settings are backed up and restored in the order they appear
- * in this array. If you have one setting depending on another,
- * make sure that they are ordered appropriately.
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ * @param def Value to return if the setting is not defined.
*
- * @hide
+ * @return The setting's current value, or 'def' if it is not defined
+ * or not a valid float.
*/
- public static final String[] SETTINGS_TO_BACKUP = {
- ADB_ENABLED,
- ALLOW_MOCK_LOCATION,
- PARENTAL_CONTROL_ENABLED,
- PARENTAL_CONTROL_REDIRECT_URL,
- USB_MASS_STORAGE_ENABLED,
- ACCESSIBILITY_SCRIPT_INJECTION,
- BACKUP_AUTO_RESTORE,
- ENABLED_ACCESSIBILITY_SERVICES,
- TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
- TOUCH_EXPLORATION_ENABLED,
- ACCESSIBILITY_ENABLED,
- ACCESSIBILITY_SPEAK_PASSWORD,
- TTS_USE_DEFAULTS,
- TTS_DEFAULT_RATE,
- TTS_DEFAULT_PITCH,
- TTS_DEFAULT_SYNTH,
- TTS_DEFAULT_LANG,
- TTS_DEFAULT_COUNTRY,
- TTS_ENABLED_PLUGINS,
- TTS_DEFAULT_LOCALE,
- WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
- WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
- WIFI_NUM_OPEN_NETWORKS_KEPT,
- MOUNT_PLAY_NOTIFICATION_SND,
- MOUNT_UMS_AUTOSTART,
- MOUNT_UMS_PROMPT,
- MOUNT_UMS_NOTIFY_ENABLED,
- UI_NIGHT_MODE,
- LOCK_SCREEN_OWNER_INFO,
- LOCK_SCREEN_OWNER_INFO_ENABLED
- };
+ public static float getFloat(ContentResolver cr, String name, float def) {
+ String v = getString(cr, name);
+ try {
+ return v != null ? Float.parseFloat(v) : def;
+ } catch (NumberFormatException e) {
+ return def;
+ }
+ }
/**
- * 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
+ * Convenience function for retrieving a single secure settings value
+ * as a float. Note that internally setting values are always
+ * stored as strings; this function converts the string to a float
+ * for you.
+ * <p>
+ * This version does not take a default value. If the setting has not
+ * been set, or the string value is not a number,
+ * it throws {@link SettingNotFoundException}.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to retrieve.
+ *
+ * @throws SettingNotFoundException Thrown if a setting by the given
+ * name can't be found or the setting value is not a float.
+ *
+ * @return The setting's current value.
*/
- public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
- String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED);
- return TextUtils.delimitedStringContains(allowedProviders, ',', provider);
+ public static float getFloat(ContentResolver cr, String name)
+ throws SettingNotFoundException {
+ String v = getString(cr, name);
+ if (v == null) {
+ throw new SettingNotFoundException(name);
+ }
+ try {
+ return Float.parseFloat(v);
+ } catch (NumberFormatException e) {
+ throw new SettingNotFoundException(name);
+ }
}
/**
- * 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
+ * Convenience function for updating a single settings value as a
+ * floating point number. This will either create a new entry in the
+ * table if the given name does not exist, or modify the value of the
+ * existing row with that name. Note that internally setting values
+ * are always stored as strings, so this function converts the given
+ * value to a string before storing it.
+ *
+ * @param cr The ContentResolver to access.
+ * @param name The name of the setting to modify.
+ * @param value The new value for the setting.
+ * @return true if the value was set, false on database errors
*/
- public static final void setLocationProviderEnabled(ContentResolver cr,
- String provider, boolean enabled) {
- // 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;
- }
- putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider);
+ public static boolean putFloat(ContentResolver cr, String name, float value) {
+ return putString(cr, name, Float.toString(value));
}
}
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
deleted file mode 100644
index 08a99d2..0000000
--- a/core/java/android/server/BluetoothA2dpService.java
+++ /dev/null
@@ -1,653 +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.
- */
-
-/**
- * TODO: Move this to services.jar
- * and make the constructor package private again.
- * @hide
- */
-
-package android.server;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
-import android.bluetooth.IBluetoothA2dp;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-
-public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
- private static final String TAG = "BluetoothA2dpService";
- private static final boolean DBG = true;
-
- public static final String BLUETOOTH_A2DP_SERVICE = "bluetooth_a2dp";
-
- private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
- private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
-
- private static final String BLUETOOTH_ENABLED = "bluetooth_enabled";
-
- private static final String PROPERTY_STATE = "State";
-
- private final Context mContext;
- private final IntentFilter mIntentFilter;
- private HashMap<BluetoothDevice, Integer> mAudioDevices;
- private final AudioManager mAudioManager;
- private final BluetoothService mBluetoothService;
- private final BluetoothAdapter mAdapter;
- private int mTargetA2dpState;
- private BluetoothDevice mPlayingA2dpDevice;
- private IntentBroadcastHandler mIntentBroadcastHandler;
- private final WakeLock mWakeLock;
-
- private static final int MSG_CONNECTION_STATE_CHANGED = 0;
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- BluetoothDevice device =
- intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
- int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
- BluetoothAdapter.ERROR);
- switch (state) {
- case BluetoothAdapter.STATE_ON:
- onBluetoothEnable();
- break;
- case BluetoothAdapter.STATE_TURNING_OFF:
- onBluetoothDisable();
- break;
- }
- } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
- synchronized (this) {
- if (mAudioDevices.containsKey(device)) {
- int state = mAudioDevices.get(device);
- handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
- }
- }
- } else if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
- int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
- if (streamType == AudioManager.STREAM_MUSIC) {
- List<BluetoothDevice> sinks = getConnectedDevices();
-
- if (sinks.size() != 0 && isPhoneDocked(sinks.get(0))) {
- String address = sinks.get(0).getAddress();
- int newVolLevel =
- intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
- int oldVolLevel =
- intent.getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
- String path = mBluetoothService.getObjectPathFromAddress(address);
- if (newVolLevel > oldVolLevel) {
- avrcpVolumeUpNative(path);
- } else if (newVolLevel < oldVolLevel) {
- avrcpVolumeDownNative(path);
- }
- }
- }
- }
- }
- };
-
- private boolean isPhoneDocked(BluetoothDevice device) {
- // This works only because these broadcast intents are "sticky"
- Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
- if (i != null) {
- int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
- if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- BluetoothDevice dockDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- if (dockDevice != null && device.equals(dockDevice)) {
- return true;
- }
- }
- }
- return false;
- }
-
- public BluetoothA2dpService(Context context, BluetoothService bluetoothService) {
- mContext = context;
-
- PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothA2dpService");
-
- mIntentBroadcastHandler = new IntentBroadcastHandler();
-
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-
- mBluetoothService = bluetoothService;
- if (mBluetoothService == null) {
- throw new RuntimeException("Platform does not support Bluetooth");
- }
-
- if (!initNative()) {
- throw new RuntimeException("Could not init BluetoothA2dpService");
- }
-
- mAdapter = BluetoothAdapter.getDefaultAdapter();
-
- mIntentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
- mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
- mIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
- mIntentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
- mContext.registerReceiver(mReceiver, mIntentFilter);
-
- mAudioDevices = new HashMap<BluetoothDevice, Integer>();
-
- if (mBluetoothService.isEnabled())
- onBluetoothEnable();
- mTargetA2dpState = -1;
- mBluetoothService.setA2dpService(this);
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- cleanupNative();
- } finally {
- super.finalize();
- }
- }
-
- private int convertBluezSinkStringToState(String value) {
- if (value.equalsIgnoreCase("disconnected"))
- return BluetoothA2dp.STATE_DISCONNECTED;
- if (value.equalsIgnoreCase("connecting"))
- return BluetoothA2dp.STATE_CONNECTING;
- if (value.equalsIgnoreCase("connected"))
- return BluetoothA2dp.STATE_CONNECTED;
- if (value.equalsIgnoreCase("playing"))
- return BluetoothA2dp.STATE_PLAYING;
- return -1;
- }
-
- private boolean isSinkDevice(BluetoothDevice device) {
- ParcelUuid[] uuids = mBluetoothService.getRemoteUuids(device.getAddress());
- if (uuids != null && BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSink)) {
- return true;
- }
- return false;
- }
-
- private synchronized void addAudioSink(BluetoothDevice device) {
- if (mAudioDevices.get(device) == null) {
- mAudioDevices.put(device, BluetoothA2dp.STATE_DISCONNECTED);
- }
- }
-
- private synchronized void onBluetoothEnable() {
- String devices = mBluetoothService.getProperty("Devices", true);
- if (devices != null) {
- String [] paths = devices.split(",");
- for (String path: paths) {
- String address = mBluetoothService.getAddressFromObjectPath(path);
- BluetoothDevice device = mAdapter.getRemoteDevice(address);
- ParcelUuid[] remoteUuids = mBluetoothService.getRemoteUuids(address);
- if (remoteUuids != null)
- if (BluetoothUuid.containsAnyUuid(remoteUuids,
- new ParcelUuid[] {BluetoothUuid.AudioSink,
- BluetoothUuid.AdvAudioDist})) {
- addAudioSink(device);
- }
- }
- }
- mAudioManager.setParameters(BLUETOOTH_ENABLED+"=true");
- mAudioManager.setParameters("A2dpSuspended=false");
- }
-
- private synchronized void onBluetoothDisable() {
- if (!mAudioDevices.isEmpty()) {
- BluetoothDevice[] devices = new BluetoothDevice[mAudioDevices.size()];
- devices = mAudioDevices.keySet().toArray(devices);
- for (BluetoothDevice device : devices) {
- int state = getConnectionState(device);
- switch (state) {
- case BluetoothA2dp.STATE_CONNECTING:
- case BluetoothA2dp.STATE_CONNECTED:
- case BluetoothA2dp.STATE_PLAYING:
- disconnectSinkNative(mBluetoothService.getObjectPathFromAddress(
- device.getAddress()));
- handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
- break;
- case BluetoothA2dp.STATE_DISCONNECTING:
- handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTING,
- BluetoothA2dp.STATE_DISCONNECTED);
- break;
- }
- }
- mAudioDevices.clear();
- }
-
- mAudioManager.setParameters(BLUETOOTH_ENABLED + "=false");
- }
-
- private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) {
- if (!mBluetoothService.isEnabled() || !isSinkDevice(device) ||
- getPriority(device) == BluetoothA2dp.PRIORITY_OFF) {
- return false;
- }
-
- addAudioSink(device);
-
- String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
- if (path == null) {
- return false;
- }
- return true;
- }
-
- public synchronized boolean isA2dpPlaying(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (DBG) log("isA2dpPlaying(" + device + ")");
- if (device.equals(mPlayingA2dpDevice)) return true;
- return false;
- }
-
- public synchronized boolean connect(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (DBG) log("connectSink(" + device + ")");
- if (!isConnectSinkFeasible(device)) return false;
-
- for (BluetoothDevice sinkDevice : mAudioDevices.keySet()) {
- if (getConnectionState(sinkDevice) != BluetoothProfile.STATE_DISCONNECTED) {
- disconnect(sinkDevice);
- }
- }
-
- return mBluetoothService.connectSink(device.getAddress());
- }
-
- public synchronized boolean connectSinkInternal(BluetoothDevice device) {
- if (!mBluetoothService.isEnabled()) return false;
-
- int state = mAudioDevices.get(device);
-
- // ignore if there are any active sinks
- if (getDevicesMatchingConnectionStates(new int[] {
- BluetoothA2dp.STATE_CONNECTING,
- BluetoothA2dp.STATE_CONNECTED,
- BluetoothA2dp.STATE_DISCONNECTING}).size() != 0) {
- return false;
- }
-
- switch (state) {
- case BluetoothA2dp.STATE_CONNECTED:
- case BluetoothA2dp.STATE_DISCONNECTING:
- return false;
- case BluetoothA2dp.STATE_CONNECTING:
- return true;
- }
-
- String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
-
- // State is DISCONNECTED and we are connecting.
- if (getPriority(device) < BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
- setPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
- }
- handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING);
-
- if (!connectSinkNative(path)) {
- // Restore previous state
- handleSinkStateChange(device, mAudioDevices.get(device), state);
- return false;
- }
- return true;
- }
-
- private synchronized boolean isDisconnectSinkFeasible(BluetoothDevice device) {
- String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
- if (path == null) {
- return false;
- }
-
- int state = getConnectionState(device);
- switch (state) {
- case BluetoothA2dp.STATE_DISCONNECTED:
- case BluetoothA2dp.STATE_DISCONNECTING:
- return false;
- }
- return true;
- }
-
- public synchronized boolean disconnect(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (DBG) log("disconnectSink(" + device + ")");
- if (!isDisconnectSinkFeasible(device)) return false;
- return mBluetoothService.disconnectSink(device.getAddress());
- }
-
- public synchronized boolean disconnectSinkInternal(BluetoothDevice device) {
- int state = getConnectionState(device);
- String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
-
- switch (state) {
- case BluetoothA2dp.STATE_DISCONNECTED:
- case BluetoothA2dp.STATE_DISCONNECTING:
- return false;
- }
- // State is CONNECTING or CONNECTED or PLAYING
- handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING);
- if (!disconnectSinkNative(path)) {
- // Restore previous state
- handleSinkStateChange(device, mAudioDevices.get(device), state);
- return false;
- }
- return true;
- }
-
- public synchronized boolean suspendSink(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (DBG) log("suspendSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
- if (device == null || mAudioDevices == null) {
- return false;
- }
- String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
- Integer state = mAudioDevices.get(device);
- if (path == null || state == null) {
- return false;
- }
-
- mTargetA2dpState = BluetoothA2dp.STATE_CONNECTED;
- return checkSinkSuspendState(state.intValue());
- }
-
- public synchronized boolean resumeSink(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (DBG) log("resumeSink(" + device + "), mTargetA2dpState: "+mTargetA2dpState);
- if (device == null || mAudioDevices == null) {
- return false;
- }
- String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
- Integer state = mAudioDevices.get(device);
- if (path == null || state == null) {
- return false;
- }
- mTargetA2dpState = BluetoothA2dp.STATE_PLAYING;
- return checkSinkSuspendState(state.intValue());
- }
-
- public synchronized int getConnectionState(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- Integer state = mAudioDevices.get(device);
- if (state == null)
- return BluetoothA2dp.STATE_DISCONNECTED;
- return state;
- }
-
- public synchronized List<BluetoothDevice> getConnectedDevices() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- List<BluetoothDevice> sinks = getDevicesMatchingConnectionStates(
- new int[] {BluetoothA2dp.STATE_CONNECTED});
- return sinks;
- }
-
- public synchronized List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- ArrayList<BluetoothDevice> sinks = new ArrayList<BluetoothDevice>();
- for (BluetoothDevice device: mAudioDevices.keySet()) {
- int sinkState = getConnectionState(device);
- for (int state : states) {
- if (state == sinkState) {
- sinks.add(device);
- break;
- }
- }
- }
- return sinks;
- }
-
- public synchronized int getPriority(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()),
- BluetoothA2dp.PRIORITY_UNDEFINED);
- }
-
- public synchronized boolean setPriority(BluetoothDevice device, int priority) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- return Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority);
- }
-
- public synchronized boolean allowIncomingConnect(BluetoothDevice device, boolean value) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- String address = device.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return false;
- }
- Integer data = mBluetoothService.getAuthorizationAgentRequestData(address);
- if (data == null) {
- Log.w(TAG, "allowIncomingConnect(" + device + ") called but no native data available");
- return false;
- }
- log("allowIncomingConnect: A2DP: " + device + ":" + value);
- return mBluetoothService.setAuthorizationNative(address, value, data.intValue());
- }
-
- /**
- * Called by native code on a PropertyChanged signal from
- * org.bluez.AudioSink.
- *
- * @param path the object path for the changed device
- * @param propValues a string array containing the key and one or more
- * values.
- */
- private synchronized void onSinkPropertyChanged(String path, String[] propValues) {
- if (!mBluetoothService.isEnabled()) {
- return;
- }
-
- String name = propValues[0];
- String address = mBluetoothService.getAddressFromObjectPath(path);
- if (address == null) {
- Log.e(TAG, "onSinkPropertyChanged: Address of the remote device in null");
- return;
- }
-
- BluetoothDevice device = mAdapter.getRemoteDevice(address);
-
- if (name.equals(PROPERTY_STATE)) {
- int state = convertBluezSinkStringToState(propValues[1]);
- log("A2DP: onSinkPropertyChanged newState is: " + state + "mPlayingA2dpDevice: " + mPlayingA2dpDevice);
-
- if (mAudioDevices.get(device) == null) {
- // This is for an incoming connection for a device not known to us.
- // We have authorized it and bluez state has changed.
- addAudioSink(device);
- handleSinkStateChange(device, BluetoothA2dp.STATE_DISCONNECTED, state);
- } else {
- if (state == BluetoothA2dp.STATE_PLAYING && mPlayingA2dpDevice == null) {
- mPlayingA2dpDevice = device;
- handleSinkPlayingStateChange(device, state, BluetoothA2dp.STATE_NOT_PLAYING);
- } else if (state == BluetoothA2dp.STATE_CONNECTED && mPlayingA2dpDevice != null) {
- mPlayingA2dpDevice = null;
- handleSinkPlayingStateChange(device, BluetoothA2dp.STATE_NOT_PLAYING,
- BluetoothA2dp.STATE_PLAYING);
- } else {
- mPlayingA2dpDevice = null;
- int prevState = mAudioDevices.get(device);
- handleSinkStateChange(device, prevState, state);
- }
- }
- }
- }
-
- private void handleSinkStateChange(BluetoothDevice device, int prevState, int state) {
- if (state != prevState) {
- mAudioDevices.put(device, state);
-
- checkSinkSuspendState(state);
- mTargetA2dpState = -1;
-
- if (getPriority(device) > BluetoothA2dp.PRIORITY_OFF &&
- state == BluetoothA2dp.STATE_CONNECTED) {
- // We have connected or attempting to connect.
- // Bump priority
- setPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
- // We will only have 1 device with AUTO_CONNECT priority
- // To be backward compatible set everyone else to have PRIORITY_ON
- adjustOtherSinkPriorities(device);
- }
-
- int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, state);
-
- mWakeLock.acquire();
- mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.obtainMessage(
- MSG_CONNECTION_STATE_CHANGED,
- prevState,
- state,
- device),
- delay);
- }
- }
-
- private void handleSinkPlayingStateChange(BluetoothDevice device, int state, int prevState) {
- Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
-
- if (DBG) log("A2DP Playing state : device: " + device + " State:" + prevState + "->" + state);
- }
-
- private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) {
- for (BluetoothDevice device : mAdapter.getBondedDevices()) {
- if (getPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
- !device.equals(connectedDevice)) {
- setPriority(device, BluetoothA2dp.PRIORITY_ON);
- }
- }
- }
-
- private boolean checkSinkSuspendState(int state) {
- boolean result = true;
-
- if (state != mTargetA2dpState) {
- if (state == BluetoothA2dp.STATE_PLAYING &&
- mTargetA2dpState == BluetoothA2dp.STATE_CONNECTED) {
- mAudioManager.setParameters("A2dpSuspended=true");
- } else if (state == BluetoothA2dp.STATE_CONNECTED &&
- mTargetA2dpState == BluetoothA2dp.STATE_PLAYING) {
- mAudioManager.setParameters("A2dpSuspended=false");
- } else {
- result = false;
- }
- }
- return result;
- }
-
- /**
- * Called by native code for the async response to a Connect
- * method call to org.bluez.AudioSink.
- *
- * @param deviceObjectPath the object path for the connecting device
- * @param result true on success; false on error
- */
- private void onConnectSinkResult(String deviceObjectPath, boolean result) {
- // If the call was a success, ignore we will update the state
- // when we a Sink Property Change
- if (!result) {
- if (deviceObjectPath != null) {
- String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
- if (address == null) return;
- BluetoothDevice device = mAdapter.getRemoteDevice(address);
- int state = getConnectionState(device);
- handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
- }
- }
- }
-
- /** Handles A2DP connection state change intent broadcasts. */
- private class IntentBroadcastHandler extends Handler {
-
- private void onConnectionStateChanged(BluetoothDevice device, int prevState, int state) {
- Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothProfile.EXTRA_STATE, state);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
-
- if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state);
-
- mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.A2DP, state,
- prevState);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_CONNECTION_STATE_CHANGED:
- onConnectionStateChanged((BluetoothDevice) msg.obj, msg.arg1, msg.arg2);
- mWakeLock.release();
- break;
- }
- }
- }
-
- @Override
- protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
-
- if (mAudioDevices.isEmpty()) return;
- pw.println("Cached audio devices:");
- for (BluetoothDevice device : mAudioDevices.keySet()) {
- int state = mAudioDevices.get(device);
- pw.println(device + " " + BluetoothA2dp.stateToString(state));
- }
- }
-
- private static void log(String msg) {
- Log.d(TAG, msg);
- }
-
- private native boolean initNative();
- private native void cleanupNative();
- private synchronized native boolean connectSinkNative(String path);
- private synchronized native boolean disconnectSinkNative(String path);
- private synchronized native boolean suspendSinkNative(String path);
- private synchronized native boolean resumeSinkNative(String path);
- private synchronized native Object []getSinkPropertiesNative(String path);
- private synchronized native boolean avrcpVolumeUpNative(String path);
- private synchronized native boolean avrcpVolumeDownNative(String path);
-}
diff --git a/core/java/android/server/BluetoothAdapterProperties.java b/core/java/android/server/BluetoothAdapterProperties.java
deleted file mode 100644
index 9723f60..0000000
--- a/core/java/android/server/BluetoothAdapterProperties.java
+++ /dev/null
@@ -1,105 +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 android.server;
-
-import android.content.Context;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Map;
-
-class BluetoothAdapterProperties {
-
- private static final String TAG = "BluetoothAdapterProperties";
-
- private final Map<String, String> mPropertiesMap;
- private final Context mContext;
- private final BluetoothService mService;
-
- BluetoothAdapterProperties(Context context, BluetoothService service) {
- mPropertiesMap = new HashMap<String, String>();
- mContext = context;
- mService = service;
- }
-
- synchronized String getProperty(String name) {
- if (mPropertiesMap.isEmpty()) {
- getAllProperties();
- }
- return mPropertiesMap.get(name);
- }
-
- String getObjectPath() {
- return getProperty("ObjectPath");
- }
-
- synchronized void clear() {
- mPropertiesMap.clear();
- }
-
- synchronized boolean isEmpty() {
- return mPropertiesMap.isEmpty();
- }
-
- synchronized void setProperty(String name, String value) {
- mPropertiesMap.put(name, value);
- }
-
- synchronized void getAllProperties() {
- mContext.enforceCallingOrSelfPermission(
- BluetoothService.BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
- mPropertiesMap.clear();
-
- String properties[] = (String[]) mService
- .getAdapterPropertiesNative();
- // The String Array consists of key-value pairs.
- if (properties == null) {
- Log.e(TAG, "*Error*: GetAdapterProperties returned NULL");
- return;
- }
-
- for (int i = 0; i < properties.length; i++) {
- String name = properties[i];
- String newValue = null;
- if (name == null) {
- Log.e(TAG, "Error:Adapter Property at index " + i + " is null");
- continue;
- }
- if (name.equals("Devices") || name.equals("UUIDs")) {
- StringBuilder str = new StringBuilder();
- int len = Integer.valueOf(properties[++i]);
- for (int j = 0; j < len; j++) {
- str.append(properties[++i]);
- str.append(",");
- }
- if (len > 0) {
- newValue = str.toString();
- }
- } else {
- newValue = properties[++i];
- }
- mPropertiesMap.put(name, newValue);
- }
-
- // Add adapter object path property.
- String adapterPath = mService.getAdapterPathNative();
- if (adapterPath != null) {
- mPropertiesMap.put("ObjectPath", adapterPath + "/dev_");
- }
- }
-}
diff --git a/core/java/android/server/BluetoothAdapterStateMachine.java b/core/java/android/server/BluetoothAdapterStateMachine.java
deleted file mode 100644
index 1de1839..0000000
--- a/core/java/android/server/BluetoothAdapterStateMachine.java
+++ /dev/null
@@ -1,822 +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.server;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.IBluetoothStateChangeCallback;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.Message;
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.util.Log;
-
-import com.android.internal.util.IState;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
-
-import java.io.PrintWriter;
-
-/**
- * Bluetooth Adapter StateMachine
- * All the states are at the same level, ie, no hierarchy.
- * (BluetootOn)<----------------------<-
- * | ^ -------------------->- |
- * | | | |
- * USER_TURN_OFF | | SCAN_MODE_CHANGED m1 | | USER_TURN_ON
- * AIRPLANE_MODE_ON | | | |
- * V | | |
- * (Switching) (PerProcessState)
- * | ^ | |
- * POWER_STATE_CHANGED & | | TURN_ON(_CONTINUE) | |
- * ALL_DEVICES_DISCONNECTED | | m2 | |
- * V |------------------------< | SCAN_MODE_CHANGED
- * (HotOff)-------------------------->- PER_PROCESS_TURN_ON
- * / ^
- * / | SERVICE_RECORD_LOADED
- * | |
- * TURN_COLD | (Warmup)
- * \ ^
- * \ | TURN_HOT/TURN_ON
- * | | AIRPLANE_MODE_OFF(when Bluetooth was on before)
- * V |
- * (PowerOff) <----- initial state
- *
- * Legend:
- * m1 = TURN_HOT
- * m2 = Transition to HotOff when number of process wanting BT on is 0.
- * POWER_STATE_CHANGED will make the transition.
- * Note:
- * The diagram above shows all the states and messages that trigger normal state changes.
- * The diagram above does not capture everything:
- * The diagram does not capture following messages.
- * - messages that do not trigger state changes
- * For example, PER_PROCESS_TURN_ON received in BluetoothOn state
- * - unhandled messages
- * For example, USER_TURN_ON received in BluetoothOn state
- * - timeout messages
- * The diagram does not capture error conditions and state recoveries.
- * - For example POWER_STATE_CHANGED received in BluetoothOn state
- */
-final class BluetoothAdapterStateMachine extends StateMachine {
- private static final String TAG = "BluetoothAdapterStateMachine";
- private static final boolean DBG = false;
-
- // Message(what) to take an action
- //
- // We get this message when user tries to turn on BT
- static final int USER_TURN_ON = 1;
- // We get this message when user tries to turn off BT
- static final int USER_TURN_OFF = 2;
- // Per process enable / disable messages
- static final int PER_PROCESS_TURN_ON = 3;
- static final int PER_PROCESS_TURN_OFF = 4;
-
- // Turn on Bluetooth Module, Load firmware, and do all the preparation
- // needed to get the Bluetooth Module ready but keep it not discoverable
- // and not connectable. This way the Bluetooth Module can be quickly
- // switched on if needed
- static final int TURN_HOT = 5;
-
- // Message(what) to report a event that the state machine need to respond to
- //
- // Event indicates sevice records have been loaded
- static final int SERVICE_RECORD_LOADED = 51;
- // Event indicates all the remote Bluetooth devices has been disconnected
- static final int ALL_DEVICES_DISCONNECTED = 52;
- // Event indicates the Bluetooth scan mode has changed
- static final int SCAN_MODE_CHANGED = 53;
- // Event indicates the powered state has changed
- static final int POWER_STATE_CHANGED = 54;
- // Event indicates airplane mode is turned on
- static final int AIRPLANE_MODE_ON = 55;
- // Event indicates airplane mode is turned off
- static final int AIRPLANE_MODE_OFF = 56;
-
- // private internal messages
- //
- // USER_TURN_ON is changed to TURN_ON_CONTINUE after we broadcast the
- // state change intent so that we will not broadcast the intent again in
- // other state
- private static final int TURN_ON_CONTINUE = 101;
- // Unload firmware, turning off Bluetooth module power
- private static final int TURN_COLD = 102;
- // Device disconnecting timeout happens
- private static final int DEVICES_DISCONNECT_TIMEOUT = 103;
- // Prepare Bluetooth timeout happens
- private static final int PREPARE_BLUETOOTH_TIMEOUT = 104;
- // Bluetooth turn off wait timeout happens
- private static final int TURN_OFF_TIMEOUT = 105;
- // Bluetooth device power off wait timeout happens
- private static final int POWER_DOWN_TIMEOUT = 106;
-
- private Context mContext;
- private BluetoothService mBluetoothService;
- private BluetoothEventLoop mEventLoop;
-
- private BluetoothOn mBluetoothOn;
- private Switching mSwitching;
- private HotOff mHotOff;
- private WarmUp mWarmUp;
- private PowerOff mPowerOff;
- private PerProcessState mPerProcessState;
-
- // this is the BluetoothAdapter state that reported externally
- private int mPublicState;
- // When turning off, broadcast STATE_OFF in the last HotOff state
- // This is because we do HotOff -> PowerOff -> HotOff for USER_TURN_OFF
- private boolean mDelayBroadcastStateOff;
-
- // timeout value waiting for all the devices to be disconnected
- private static final int DEVICES_DISCONNECT_TIMEOUT_TIME = 3000;
-
- private static final int PREPARE_BLUETOOTH_TIMEOUT_TIME = 10000;
-
- private static final int TURN_OFF_TIMEOUT_TIME = 5000;
- private static final int POWER_DOWN_TIMEOUT_TIME = 20;
-
- BluetoothAdapterStateMachine(Context context, BluetoothService bluetoothService,
- BluetoothAdapter bluetoothAdapter) {
- super(TAG);
- mContext = context;
- mBluetoothService = bluetoothService;
- mEventLoop = new BluetoothEventLoop(context, bluetoothAdapter, bluetoothService, this);
-
- mBluetoothOn = new BluetoothOn();
- mSwitching = new Switching();
- mHotOff = new HotOff();
- mWarmUp = new WarmUp();
- mPowerOff = new PowerOff();
- mPerProcessState = new PerProcessState();
-
- addState(mBluetoothOn);
- addState(mSwitching);
- addState(mHotOff);
- addState(mWarmUp);
- addState(mPowerOff);
- addState(mPerProcessState);
-
- setInitialState(mPowerOff);
- mPublicState = BluetoothAdapter.STATE_OFF;
- mDelayBroadcastStateOff = false;
- }
-
- /**
- * Bluetooth module's power is off, firmware is not loaded.
- */
- private class PowerOff extends State {
- @Override
- public void enter() {
- if (DBG) log("Enter PowerOff: " + getCurrentMessage().what);
- }
- @Override
- public boolean processMessage(Message message) {
- log("PowerOff process message: " + message.what);
-
- boolean retValue = HANDLED;
- switch(message.what) {
- case USER_TURN_ON:
- // starts turning on BT module, broadcast this out
- broadcastState(BluetoothAdapter.STATE_TURNING_ON);
- transitionTo(mWarmUp);
- if (prepareBluetooth()) {
- // this is user request, save the setting
- if ((Boolean) message.obj) {
- persistSwitchSetting(true);
- }
- // We will continue turn the BT on all the way to the BluetoothOn state
- deferMessage(obtainMessage(TURN_ON_CONTINUE));
- } else {
- Log.e(TAG, "failed to prepare bluetooth, abort turning on");
- transitionTo(mPowerOff);
- broadcastState(BluetoothAdapter.STATE_OFF);
- }
- break;
- case TURN_HOT:
- if (prepareBluetooth()) {
- transitionTo(mWarmUp);
- }
- break;
- case AIRPLANE_MODE_OFF:
- if (getBluetoothPersistedSetting()) {
- // starts turning on BT module, broadcast this out
- broadcastState(BluetoothAdapter.STATE_TURNING_ON);
- transitionTo(mWarmUp);
- if (prepareBluetooth()) {
- // We will continue turn the BT on all the way to the BluetoothOn state
- deferMessage(obtainMessage(TURN_ON_CONTINUE));
- transitionTo(mWarmUp);
- } else {
- Log.e(TAG, "failed to prepare bluetooth, abort turning on");
- transitionTo(mPowerOff);
- broadcastState(BluetoothAdapter.STATE_OFF);
- }
- } else if (mContext.getResources().getBoolean
- (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
- sendMessage(TURN_HOT);
- }
- break;
- case PER_PROCESS_TURN_ON:
- if (prepareBluetooth()) {
- transitionTo(mWarmUp);
- }
- deferMessage(obtainMessage(PER_PROCESS_TURN_ON));
- break;
- case PER_PROCESS_TURN_OFF:
- perProcessCallback(false, (IBluetoothStateChangeCallback) message.obj);
- break;
- case USER_TURN_OFF:
- Log.w(TAG, "PowerOff received: " + message.what);
- case AIRPLANE_MODE_ON: // ignore
- break;
- default:
- return NOT_HANDLED;
- }
- return retValue;
- }
-
- /**
- * Turn on Bluetooth Module, Load firmware, and do all the preparation
- * needed to get the Bluetooth Module ready but keep it not discoverable
- * and not connectable.
- * The last step of this method sets up the local service record DB.
- * There will be a event reporting the status of the SDP setup.
- */
- private boolean prepareBluetooth() {
- if (mBluetoothService.enableNative() != 0) {
- return false;
- }
-
- // try to start event loop, give 2 attempts
- int retryCount = 2;
- boolean eventLoopStarted = false;
- while ((retryCount-- > 0) && !eventLoopStarted) {
- mEventLoop.start();
- // it may take a moment for the other thread to do its
- // thing. Check periodically for a while.
- int pollCount = 5;
- while ((pollCount-- > 0) && !eventLoopStarted) {
- if (mEventLoop.isEventLoopRunning()) {
- eventLoopStarted = true;
- break;
- }
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- log("prepareBluetooth sleep interrupted: " + pollCount);
- break;
- }
- }
- }
-
- if (!eventLoopStarted) {
- mBluetoothService.disableNative();
- return false;
- }
-
- // get BluetoothService ready
- if (!mBluetoothService.prepareBluetooth()) {
- mEventLoop.stop();
- mBluetoothService.disableNative();
- return false;
- }
-
- sendMessageDelayed(PREPARE_BLUETOOTH_TIMEOUT, PREPARE_BLUETOOTH_TIMEOUT_TIME);
- return true;
- }
- }
-
- /**
- * Turning on Bluetooth module's power, loading firmware, starting
- * event loop thread to listen on Bluetooth module event changes.
- */
- private class WarmUp extends State {
-
- @Override
- public void enter() {
- if (DBG) log("Enter WarmUp: " + getCurrentMessage().what);
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("WarmUp process message: " + message.what);
-
- boolean retValue = HANDLED;
- switch(message.what) {
- case SERVICE_RECORD_LOADED:
- removeMessages(PREPARE_BLUETOOTH_TIMEOUT);
- transitionTo(mHotOff);
- if (mDelayBroadcastStateOff) {
- broadcastState(BluetoothAdapter.STATE_OFF);
- mDelayBroadcastStateOff = false;
- }
- break;
- case PREPARE_BLUETOOTH_TIMEOUT:
- Log.e(TAG, "Bluetooth adapter SDP failed to load");
- shutoffBluetooth();
- transitionTo(mPowerOff);
- broadcastState(BluetoothAdapter.STATE_OFF);
- break;
- case USER_TURN_ON: // handle this at HotOff state
- case TURN_ON_CONTINUE: // Once in HotOff state, continue turn bluetooth
- // on to the BluetoothOn state
- case AIRPLANE_MODE_ON:
- case AIRPLANE_MODE_OFF:
- case PER_PROCESS_TURN_ON:
- case PER_PROCESS_TURN_OFF:
- deferMessage(message);
- break;
- case USER_TURN_OFF:
- Log.w(TAG, "WarmUp received: " + message.what);
- break;
- default:
- return NOT_HANDLED;
- }
- return retValue;
- }
-
- }
-
- /**
- * Bluetooth Module has powered, firmware loaded, event loop started,
- * SDP loaded, but the modules stays non-discoverable and
- * non-connectable.
- */
- private class HotOff extends State {
- @Override
- public void enter() {
- if (DBG) log("Enter HotOff: " + getCurrentMessage().what);
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("HotOff process message: " + message.what);
-
- boolean retValue = HANDLED;
- switch(message.what) {
- case USER_TURN_ON:
- broadcastState(BluetoothAdapter.STATE_TURNING_ON);
- if ((Boolean) message.obj) {
- persistSwitchSetting(true);
- }
- // let it fall to TURN_ON_CONTINUE:
- //$FALL-THROUGH$
- case TURN_ON_CONTINUE:
- mBluetoothService.switchConnectable(true);
- transitionTo(mSwitching);
- break;
- case AIRPLANE_MODE_ON:
- case TURN_COLD:
- shutoffBluetooth();
- // we cannot go to power off state yet, we need wait for the Bluetooth
- // device power off. Unfortunately the stack does not give a event back
- // so we wait a little bit here
- sendMessageDelayed(POWER_DOWN_TIMEOUT,
- POWER_DOWN_TIMEOUT_TIME);
- break;
- case POWER_DOWN_TIMEOUT:
- transitionTo(mPowerOff);
- if (!mDelayBroadcastStateOff) {
- broadcastState(BluetoothAdapter.STATE_OFF);
- }
- break;
- case AIRPLANE_MODE_OFF:
- if (getBluetoothPersistedSetting()) {
- broadcastState(BluetoothAdapter.STATE_TURNING_ON);
- transitionTo(mSwitching);
- mBluetoothService.switchConnectable(true);
- }
- break;
- case PER_PROCESS_TURN_ON:
- transitionTo(mPerProcessState);
-
- // Resend the PER_PROCESS_TURN_ON message so that the callback
- // can be sent through.
- deferMessage(message);
-
- mBluetoothService.switchConnectable(true);
- break;
- case PER_PROCESS_TURN_OFF:
- perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj);
- break;
- case USER_TURN_OFF: // ignore
- break;
- case POWER_STATE_CHANGED:
- if ((Boolean) message.obj) {
- recoverStateMachine(TURN_HOT, null);
- }
- break;
- case TURN_HOT:
- deferMessage(message);
- break;
- default:
- return NOT_HANDLED;
- }
- return retValue;
- }
-
- }
-
- private class Switching extends State {
-
- @Override
- public void enter() {
- if (DBG) log("Enter Switching: " + getCurrentMessage().what);
- }
- @Override
- public boolean processMessage(Message message) {
- log("Switching process message: " + message.what);
-
- boolean retValue = HANDLED;
- switch(message.what) {
- case SCAN_MODE_CHANGED:
- // This event matches mBluetoothService.switchConnectable action
- if (mPublicState == BluetoothAdapter.STATE_TURNING_ON) {
- // set pairable if it's not
- mBluetoothService.setPairable();
- mBluetoothService.initBluetoothAfterTurningOn();
- transitionTo(mBluetoothOn);
- broadcastState(BluetoothAdapter.STATE_ON);
- // run bluetooth now that it's turned on
- // Note runBluetooth should be called only in adapter STATE_ON
- mBluetoothService.runBluetooth();
- }
- break;
- case POWER_STATE_CHANGED:
- removeMessages(TURN_OFF_TIMEOUT);
- if (!((Boolean) message.obj)) {
- if (mPublicState == BluetoothAdapter.STATE_TURNING_OFF) {
- transitionTo(mHotOff);
- mBluetoothService.finishDisable();
- mBluetoothService.cleanupAfterFinishDisable();
- deferMessage(obtainMessage(TURN_COLD));
- if (mContext.getResources().getBoolean
- (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch) &&
- !mBluetoothService.isAirplaneModeOn()) {
- deferMessage(obtainMessage(TURN_HOT));
- mDelayBroadcastStateOff = true;
- }
- }
- } else {
- if (mPublicState != BluetoothAdapter.STATE_TURNING_ON) {
- if (mContext.getResources().getBoolean
- (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
- recoverStateMachine(TURN_HOT, null);
- } else {
- recoverStateMachine(TURN_COLD, null);
- }
- }
- }
- break;
- case ALL_DEVICES_DISCONNECTED:
- removeMessages(DEVICES_DISCONNECT_TIMEOUT);
- mBluetoothService.switchConnectable(false);
- sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME);
- break;
- case DEVICES_DISCONNECT_TIMEOUT:
- sendMessage(ALL_DEVICES_DISCONNECTED);
- // reset the hardware for error recovery
- Log.e(TAG, "Devices failed to disconnect, reseting...");
- deferMessage(obtainMessage(TURN_COLD));
- if (mContext.getResources().getBoolean
- (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
- deferMessage(obtainMessage(TURN_HOT));
- }
- break;
- case TURN_OFF_TIMEOUT:
- transitionTo(mHotOff);
- finishSwitchingOff();
- // reset the hardware for error recovery
- Log.e(TAG, "Devices failed to power down, reseting...");
- deferMessage(obtainMessage(TURN_COLD));
- if (mContext.getResources().getBoolean
- (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
- deferMessage(obtainMessage(TURN_HOT));
- }
- break;
- case USER_TURN_ON:
- case AIRPLANE_MODE_OFF:
- case AIRPLANE_MODE_ON:
- case PER_PROCESS_TURN_ON:
- case PER_PROCESS_TURN_OFF:
- case USER_TURN_OFF:
- deferMessage(message);
- break;
-
- default:
- return NOT_HANDLED;
- }
- return retValue;
- }
- }
-
- private class BluetoothOn extends State {
-
- @Override
- public void enter() {
- if (DBG) log("Enter BluetoothOn: " + getCurrentMessage().what);
- }
- @Override
- public boolean processMessage(Message message) {
- log("BluetoothOn process message: " + message.what);
-
- boolean retValue = HANDLED;
- switch(message.what) {
- case USER_TURN_OFF:
- if ((Boolean) message.obj) {
- persistSwitchSetting(false);
- }
-
- if (mBluetoothService.isDiscovering()) {
- mBluetoothService.cancelDiscovery();
- }
- if (!mBluetoothService.isApplicationStateChangeTrackerEmpty()) {
- transitionTo(mPerProcessState);
- deferMessage(obtainMessage(TURN_HOT));
- break;
- }
- //$FALL-THROUGH$ to AIRPLANE_MODE_ON
- case AIRPLANE_MODE_ON:
- broadcastState(BluetoothAdapter.STATE_TURNING_OFF);
- transitionTo(mSwitching);
- if (mBluetoothService.getAdapterConnectionState() !=
- BluetoothAdapter.STATE_DISCONNECTED) {
- mBluetoothService.disconnectDevices();
- sendMessageDelayed(DEVICES_DISCONNECT_TIMEOUT,
- DEVICES_DISCONNECT_TIMEOUT_TIME);
- } else {
- mBluetoothService.switchConnectable(false);
- sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME);
- }
-
- if (message.what == AIRPLANE_MODE_ON || mBluetoothService.isAirplaneModeOn()) {
- // We inform all the per process callbacks
- allProcessesCallback(false);
- }
- break;
- case AIRPLANE_MODE_OFF:
- case USER_TURN_ON:
- Log.w(TAG, "BluetoothOn received: " + message.what);
- break;
- case PER_PROCESS_TURN_ON:
- perProcessCallback(true, (IBluetoothStateChangeCallback)message.obj);
- break;
- case PER_PROCESS_TURN_OFF:
- perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj);
- break;
- case POWER_STATE_CHANGED:
- if ((Boolean) message.obj) {
- // reset the state machine and send it TURN_ON_CONTINUE message
- recoverStateMachine(USER_TURN_ON, false);
- }
- break;
- default:
- return NOT_HANDLED;
- }
- return retValue;
- }
-
- }
-
-
- private class PerProcessState extends State {
- IBluetoothStateChangeCallback mCallback = null;
- boolean isTurningOn = false;
-
- @Override
- public void enter() {
- int what = getCurrentMessage().what;
- if (DBG) log("Enter PerProcessState: " + what);
-
- if (what == PER_PROCESS_TURN_ON) {
- isTurningOn = true;
- } else if (what == USER_TURN_OFF) {
- isTurningOn = false;
- } else {
- Log.e(TAG, "enter PerProcessState: wrong msg: " + what);
- }
- }
-
- @Override
- public boolean processMessage(Message message) {
- log("PerProcessState process message: " + message.what);
-
- boolean retValue = HANDLED;
- switch (message.what) {
- case PER_PROCESS_TURN_ON:
- mCallback = (IBluetoothStateChangeCallback)getCurrentMessage().obj;
-
- // If this is not the first application call the callback.
- if (mBluetoothService.getNumberOfApplicationStateChangeTrackers() > 1) {
- perProcessCallback(true, mCallback);
- }
- break;
- case SCAN_MODE_CHANGED:
- if (isTurningOn) {
- perProcessCallback(true, mCallback);
- isTurningOn = false;
- }
- break;
- case POWER_STATE_CHANGED:
- removeMessages(TURN_OFF_TIMEOUT);
- if (!((Boolean) message.obj)) {
- transitionTo(mHotOff);
- if (!mContext.getResources().getBoolean
- (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
- deferMessage(obtainMessage(TURN_COLD));
- }
- } else {
- if (!isTurningOn) {
- recoverStateMachine(TURN_COLD, null);
- for (IBluetoothStateChangeCallback c:
- mBluetoothService.getApplicationStateChangeCallbacks()) {
- perProcessCallback(false, c);
- deferMessage(obtainMessage(PER_PROCESS_TURN_ON, c));
- }
- }
- }
- break;
- case TURN_OFF_TIMEOUT:
- transitionTo(mHotOff);
- Log.e(TAG, "Power-down timed out, resetting...");
- deferMessage(obtainMessage(TURN_COLD));
- if (mContext.getResources().getBoolean
- (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
- deferMessage(obtainMessage(TURN_HOT));
- }
- break;
- case USER_TURN_ON:
- broadcastState(BluetoothAdapter.STATE_TURNING_ON);
- persistSwitchSetting(true);
- mBluetoothService.initBluetoothAfterTurningOn();
- transitionTo(mBluetoothOn);
- broadcastState(BluetoothAdapter.STATE_ON);
- // run bluetooth now that it's turned on
- mBluetoothService.runBluetooth();
- break;
- case TURN_HOT:
- broadcastState(BluetoothAdapter.STATE_TURNING_OFF);
- if (mBluetoothService.getAdapterConnectionState() !=
- BluetoothAdapter.STATE_DISCONNECTED) {
- mBluetoothService.disconnectDevices();
- sendMessageDelayed(DEVICES_DISCONNECT_TIMEOUT,
- DEVICES_DISCONNECT_TIMEOUT_TIME);
- break;
- }
- //$FALL-THROUGH$ all devices are already disconnected
- case ALL_DEVICES_DISCONNECTED:
- removeMessages(DEVICES_DISCONNECT_TIMEOUT);
- finishSwitchingOff();
- break;
- case DEVICES_DISCONNECT_TIMEOUT:
- finishSwitchingOff();
- Log.e(TAG, "Devices fail to disconnect, reseting...");
- transitionTo(mHotOff);
- deferMessage(obtainMessage(TURN_COLD));
- for (IBluetoothStateChangeCallback c:
- mBluetoothService.getApplicationStateChangeCallbacks()) {
- perProcessCallback(false, c);
- deferMessage(obtainMessage(PER_PROCESS_TURN_ON, c));
- }
- break;
- case PER_PROCESS_TURN_OFF:
- perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj);
- if (mBluetoothService.isApplicationStateChangeTrackerEmpty()) {
- mBluetoothService.switchConnectable(false);
- sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME);
- }
- break;
- case AIRPLANE_MODE_ON:
- mBluetoothService.switchConnectable(false);
- sendMessageDelayed(TURN_OFF_TIMEOUT, TURN_OFF_TIMEOUT_TIME);
- allProcessesCallback(false);
- break;
- case USER_TURN_OFF:
- Log.w(TAG, "PerProcessState received: " + message.what);
- break;
- default:
- return NOT_HANDLED;
- }
- return retValue;
- }
- }
-
- private void finishSwitchingOff() {
- mBluetoothService.finishDisable();
- broadcastState(BluetoothAdapter.STATE_OFF);
- mBluetoothService.cleanupAfterFinishDisable();
- }
-
- private void shutoffBluetooth() {
- mBluetoothService.shutoffBluetooth();
- mEventLoop.stop();
- mBluetoothService.cleanNativeAfterShutoffBluetooth();
- }
-
- private void perProcessCallback(boolean on, IBluetoothStateChangeCallback c) {
- if (c == null) return;
-
- try {
- c.onBluetoothStateChange(on);
- } catch (RemoteException e) {}
- }
-
- private void allProcessesCallback(boolean on) {
- for (IBluetoothStateChangeCallback c:
- mBluetoothService.getApplicationStateChangeCallbacks()) {
- perProcessCallback(on, c);
- }
- if (!on) {
- mBluetoothService.clearApplicationStateChangeTracker();
- }
- }
-
- /**
- * Return the public BluetoothAdapter state
- */
- int getBluetoothAdapterState() {
- return mPublicState;
- }
-
- BluetoothEventLoop getBluetoothEventLoop() {
- return mEventLoop;
- }
-
- private void persistSwitchSetting(boolean setOn) {
- long origCallerIdentityToken = Binder.clearCallingIdentity();
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.BLUETOOTH_ON,
- setOn ? 1 : 0);
- Binder.restoreCallingIdentity(origCallerIdentityToken);
- }
-
- private boolean getBluetoothPersistedSetting() {
- ContentResolver contentResolver = mContext.getContentResolver();
- return (Settings.Secure.getInt(contentResolver,
- Settings.Secure.BLUETOOTH_ON, 0) > 0);
- }
-
- private void broadcastState(int newState) {
-
- log("Bluetooth state " + mPublicState + " -> " + newState);
- if (mPublicState == newState) {
- return;
- }
-
- Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
- intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mPublicState);
- intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mPublicState = newState;
-
- mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM);
- }
-
- /**
- * bluetoothd has crashed and recovered, the adapter state machine has to
- * reset itself and try to return to previous state
- */
- private void recoverStateMachine(int what, Object obj) {
- Log.e(TAG, "Get unexpected power on event, reset with: " + what);
- transitionTo(mHotOff);
- deferMessage(obtainMessage(TURN_COLD));
- deferMessage(obtainMessage(what, obj));
- }
-
- private void dump(PrintWriter pw) {
- IState currentState = getCurrentState();
- if (currentState == mPowerOff) {
- pw.println("Bluetooth OFF - power down\n");
- } else if (currentState == mWarmUp) {
- pw.println("Bluetooth OFF - warm up\n");
- } else if (currentState == mHotOff) {
- pw.println("Bluetooth OFF - hot but off\n");
- } else if (currentState == mSwitching) {
- pw.println("Bluetooth Switching\n");
- } else if (currentState == mBluetoothOn) {
- pw.println("Bluetooth ON\n");
- } else {
- pw.println("ERROR: Bluetooth UNKNOWN STATE ");
- }
- }
-
- private static void log(String msg) {
- Log.d(TAG, msg);
- }
-}
diff --git a/core/java/android/server/BluetoothBondState.java b/core/java/android/server/BluetoothBondState.java
deleted file mode 100644
index 0446f02..0000000
--- a/core/java/android/server/BluetoothBondState.java
+++ /dev/null
@@ -1,497 +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 android.server;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothHeadset;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.DataInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Local cache of bonding state.
- * We keep our own state to track the intermediate state BONDING, which
- * bluez does not track.
- * All addresses must be passed in upper case.
- */
-class BluetoothBondState {
- private static final String TAG = "BluetoothBondState";
- private static final boolean DBG = true;
-
- private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
- private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();
-
- private static final String AUTO_PAIRING_BLACKLIST =
- "/etc/bluetooth/auto_pairing.conf";
- private static final String DYNAMIC_AUTO_PAIRING_BLACKLIST =
- "/data/misc/bluetooth/dynamic_auto_pairing.conf";
- private ArrayList<String> mAutoPairingAddressBlacklist;
- private ArrayList<String> mAutoPairingExactNameBlacklist;
- private ArrayList<String> mAutoPairingPartialNameBlacklist;
- private ArrayList<String> mAutoPairingFixedPinZerosKeyboardList;
- // Addresses added to blacklist dynamically based on usage.
- private ArrayList<String> mAutoPairingDynamicAddressBlacklist;
-
- // If this is an outgoing connection, store the address.
- // There can be only 1 pending outgoing connection at a time,
- private String mPendingOutgoingBonding;
-
- private final Context mContext;
- private final BluetoothService mService;
- private final BluetoothInputProfileHandler mBluetoothInputProfileHandler;
- private BluetoothA2dp mA2dpProxy;
- private BluetoothHeadset mHeadsetProxy;
-
- private ArrayList<String> mPairingRequestRcvd = new ArrayList<String>();
-
- BluetoothBondState(Context context, BluetoothService service) {
- mContext = context;
- mService = service;
- mBluetoothInputProfileHandler =
- BluetoothInputProfileHandler.getInstance(mContext, mService);
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
- mContext.registerReceiver(mReceiver, filter);
- readAutoPairingData();
- }
-
- synchronized void setPendingOutgoingBonding(String address) {
- mPendingOutgoingBonding = address;
- }
-
- public synchronized String getPendingOutgoingBonding() {
- return mPendingOutgoingBonding;
- }
-
- public synchronized void initBondState() {
- getProfileProxy();
- loadBondState();
- }
-
- private void loadBondState() {
- if (mService.getBluetoothStateInternal() !=
- BluetoothAdapter.STATE_TURNING_ON) {
- return;
- }
- String val = mService.getAdapterProperties().getProperty("Devices");
- if (val == null) {
- return;
- }
- String[] bonds = val.split(",");
- if (bonds == null) {
- return;
- }
- mState.clear();
- if (DBG) Log.d(TAG, "found " + bonds.length + " bonded devices");
- for (String device : bonds) {
- mState.put(mService.getAddressFromObjectPath(device).toUpperCase(),
- BluetoothDevice.BOND_BONDED);
- }
- }
-
- public synchronized void setBondState(String address, int state) {
- setBondState(address, state, 0);
- }
-
- /** reason is ignored unless state == BOND_NOT_BONDED */
- public synchronized void setBondState(String address, int state, int reason) {
- if (DBG) Log.d(TAG, "setBondState " + "address" + " " + state + "reason: " + reason);
-
- int oldState = getBondState(address);
- if (oldState == state) {
- return;
- }
-
- // Check if this was a pending outgoing bonding.
- // If yes, reset the state.
- if (oldState == BluetoothDevice.BOND_BONDING) {
- if (address.equals(mPendingOutgoingBonding)) {
- mPendingOutgoingBonding = null;
- }
- }
-
- if (state == BluetoothDevice.BOND_BONDED) {
- boolean setTrust = false;
- if (mPairingRequestRcvd.contains(address)) setTrust = true;
-
- mService.addProfileState(address, setTrust);
- mPairingRequestRcvd.remove(address);
-
- } else if (state == BluetoothDevice.BOND_BONDING) {
- if (mA2dpProxy == null || mHeadsetProxy == null) {
- getProfileProxy();
- }
- } else if (state == BluetoothDevice.BOND_NONE) {
- mPairingRequestRcvd.remove(address);
- }
-
- setProfilePriorities(address, state);
-
- if (DBG) {
- Log.d(TAG, address + " bond state " + oldState + " -> " + state
- + " (" + reason + ")");
- }
- Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mService.getRemoteDevice(address));
- intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, state);
- intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
- if (state == BluetoothDevice.BOND_NONE) {
- if (reason <= 0) {
- Log.w(TAG, "setBondState() called to unbond device, but reason code is " +
- "invalid. Overriding reason code with BOND_RESULT_REMOVED");
- reason = BluetoothDevice.UNBOND_REASON_REMOVED;
- }
- intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
- mState.remove(address);
- } else {
- mState.put(address, state);
- }
-
- mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM);
- }
-
- public boolean isAutoPairingBlacklisted(String address) {
- if (mAutoPairingAddressBlacklist != null) {
- for (String blacklistAddress : mAutoPairingAddressBlacklist) {
- if (address.startsWith(blacklistAddress)) return true;
- }
- }
-
- if (mAutoPairingDynamicAddressBlacklist != null) {
- for (String blacklistAddress: mAutoPairingDynamicAddressBlacklist) {
- if (address.equals(blacklistAddress)) return true;
- }
- }
-
- String name = mService.getRemoteName(address);
- if (name != null) {
- if (mAutoPairingExactNameBlacklist != null) {
- for (String blacklistName : mAutoPairingExactNameBlacklist) {
- if (name.equals(blacklistName)) return true;
- }
- }
-
- if (mAutoPairingPartialNameBlacklist != null) {
- for (String blacklistName : mAutoPairingPartialNameBlacklist) {
- if (name.startsWith(blacklistName)) return true;
- }
- }
- }
- return false;
- }
-
- public boolean isFixedPinZerosAutoPairKeyboard(String address) {
- // Note: the meaning of blacklist is reversed in this case.
- // If its in the list, we can go ahead and auto pair since
- // by default keyboard should have a variable PIN that we don't
- // auto pair using 0000.
- if (mAutoPairingFixedPinZerosKeyboardList != null) {
- for (String blacklistAddress : mAutoPairingFixedPinZerosKeyboardList) {
- if (address.startsWith(blacklistAddress)) return true;
- }
- }
- return false;
- }
-
- public synchronized int getBondState(String address) {
- Integer state = mState.get(address);
- if (state == null) {
- return BluetoothDevice.BOND_NONE;
- }
- return state.intValue();
- }
-
- /*package*/ synchronized String[] listInState(int state) {
- ArrayList<String> result = new ArrayList<String>(mState.size());
- for (Map.Entry<String, Integer> e : mState.entrySet()) {
- if (e.getValue().intValue() == state) {
- result.add(e.getKey());
- }
- }
- return result.toArray(new String[result.size()]);
- }
-
- public synchronized void addAutoPairingFailure(String address) {
- if (mAutoPairingDynamicAddressBlacklist == null) {
- mAutoPairingDynamicAddressBlacklist = new ArrayList<String>();
- }
-
- updateAutoPairingData(address);
- mAutoPairingDynamicAddressBlacklist.add(address);
- }
-
- public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
- return getAttempt(address) != 0;
- }
-
- public synchronized void clearPinAttempts(String address) {
- if (DBG) Log.d(TAG, "clearPinAttempts: " + address);
-
- mPinAttempt.remove(address);
- }
-
- public synchronized boolean hasAutoPairingFailed(String address) {
- if (mAutoPairingDynamicAddressBlacklist == null) return false;
-
- return mAutoPairingDynamicAddressBlacklist.contains(address);
- }
-
- public synchronized int getAttempt(String address) {
- Integer attempt = mPinAttempt.get(address);
- if (attempt == null) {
- return 0;
- }
- return attempt.intValue();
- }
-
- public synchronized void attempt(String address) {
- Integer attempt = mPinAttempt.get(address);
- int newAttempt;
- if (attempt == null) {
- newAttempt = 1;
- } else {
- newAttempt = attempt.intValue() + 1;
- }
- if (DBG) Log.d(TAG, "attemp newAttempt: " + newAttempt);
-
- mPinAttempt.put(address, new Integer(newAttempt));
- }
-
- private void getProfileProxy() {
- BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-
- if (mA2dpProxy == null) {
- bluetoothAdapter.getProfileProxy(mContext, mProfileServiceListener,
- BluetoothProfile.A2DP);
- }
-
- if (mHeadsetProxy == null) {
- bluetoothAdapter.getProfileProxy(mContext, mProfileServiceListener,
- BluetoothProfile.HEADSET);
- }
- }
-
- private void closeProfileProxy() {
- BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-
- if (mA2dpProxy != null) {
- bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dpProxy);
- }
-
- if (mHeadsetProxy != null) {
- bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadsetProxy);
- }
- }
-
- private BluetoothProfile.ServiceListener mProfileServiceListener =
- new BluetoothProfile.ServiceListener() {
-
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (profile == BluetoothProfile.A2DP) {
- mA2dpProxy = (BluetoothA2dp) proxy;
- } else if (profile == BluetoothProfile.HEADSET) {
- mHeadsetProxy = (BluetoothHeadset) proxy;
- }
- }
-
- public void onServiceDisconnected(int profile) {
- if (profile == BluetoothProfile.A2DP) {
- mA2dpProxy = null;
- } else if (profile == BluetoothProfile.HEADSET) {
- mHeadsetProxy = null;
- }
- }
- };
-
- private void copyAutoPairingData() {
- FileInputStream in = null;
- FileOutputStream out = null;
- try {
- File file = new File(DYNAMIC_AUTO_PAIRING_BLACKLIST);
- if (file.exists()) return;
-
- in = new FileInputStream(AUTO_PAIRING_BLACKLIST);
- out= new FileOutputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
-
- byte[] buf = new byte[1024];
- int len;
- while ((len = in.read(buf)) > 0) {
- out.write(buf, 0, len);
- }
- } catch (FileNotFoundException e) {
- Log.e(TAG, "FileNotFoundException: copyAutoPairingData " + e);
- } catch (IOException e) {
- Log.e(TAG, "IOException: copyAutoPairingData " + e);
- } finally {
- try {
- if (in != null) in.close();
- if (out != null) out.close();
- } catch (IOException e) {}
- }
- }
-
- synchronized public void readAutoPairingData() {
- if (mAutoPairingAddressBlacklist != null) return;
- copyAutoPairingData();
- FileInputStream fstream = null;
- try {
- fstream = new FileInputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
- DataInputStream in = new DataInputStream(fstream);
- BufferedReader file = new BufferedReader(new InputStreamReader(in));
- String line;
- while((line = file.readLine()) != null) {
- line = line.trim();
- if (line.length() == 0 || line.startsWith("//")) continue;
- String[] value = line.split("=");
- if (value != null && value.length == 2) {
- String[] val = value[1].split(",");
- if (value[0].equalsIgnoreCase("AddressBlacklist")) {
- mAutoPairingAddressBlacklist =
- new ArrayList<String>(Arrays.asList(val));
- } else if (value[0].equalsIgnoreCase("ExactNameBlacklist")) {
- mAutoPairingExactNameBlacklist =
- new ArrayList<String>(Arrays.asList(val));
- } else if (value[0].equalsIgnoreCase("PartialNameBlacklist")) {
- mAutoPairingPartialNameBlacklist =
- new ArrayList<String>(Arrays.asList(val));
- } else if (value[0].equalsIgnoreCase("FixedPinZerosKeyboardBlacklist")) {
- mAutoPairingFixedPinZerosKeyboardList =
- new ArrayList<String>(Arrays.asList(val));
- } else if (value[0].equalsIgnoreCase("DynamicAddressBlacklist")) {
- mAutoPairingDynamicAddressBlacklist =
- new ArrayList<String>(Arrays.asList(val));
- } else {
- Log.e(TAG, "Error parsing Auto pairing blacklist file");
- }
- }
- }
- } catch (FileNotFoundException e) {
- Log.e(TAG, "FileNotFoundException: readAutoPairingData " + e);
- } catch (IOException e) {
- Log.e(TAG, "IOException: readAutoPairingData " + e);
- } finally {
- if (fstream != null) {
- try {
- fstream.close();
- } catch (IOException e) {
- // Ignore
- }
- }
- }
- }
-
- // This function adds a bluetooth address to the auto pairing blacklist
- // file. These addresses are added to DynamicAddressBlacklistSection
- private void updateAutoPairingData(String address) {
- BufferedWriter out = null;
- try {
- out = new BufferedWriter(new FileWriter(DYNAMIC_AUTO_PAIRING_BLACKLIST, true));
- StringBuilder str = new StringBuilder();
- if (mAutoPairingDynamicAddressBlacklist.size() == 0) {
- str.append("DynamicAddressBlacklist=");
- }
- str.append(address);
- str.append(",");
- out.write(str.toString());
- } catch (FileNotFoundException e) {
- Log.e(TAG, "FileNotFoundException: updateAutoPairingData " + e);
- } catch (IOException e) {
- Log.e(TAG, "IOException: updateAutoPairingData " + e);
- } finally {
- if (out != null) {
- try {
- out.close();
- } catch (IOException e) {
- // Ignore
- }
- }
- }
- }
-
- // Set service priority of Hid, A2DP and Headset profiles depending on
- // the bond state change
- private void setProfilePriorities(String address, int state) {
- BluetoothDevice remoteDevice = mService.getRemoteDevice(address);
- // HID is handled by BluetoothService
- mBluetoothInputProfileHandler.setInitialInputDevicePriority(remoteDevice, state);
-
- // Set service priority of A2DP and Headset
- // We used to do the priority change in the 2 services after the broadcast
- // intent reach them. But that left a small time gap that could reject
- // incoming connection due to undefined priorities.
- if (state == BluetoothDevice.BOND_BONDED) {
- if (mA2dpProxy != null &&
- mA2dpProxy.getPriority(remoteDevice) == BluetoothProfile.PRIORITY_UNDEFINED) {
- mA2dpProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_ON);
- }
-
- if (mHeadsetProxy != null &&
- mHeadsetProxy.getPriority(remoteDevice) == BluetoothProfile.PRIORITY_UNDEFINED) {
- mHeadsetProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_ON);
- }
- } else if (state == BluetoothDevice.BOND_NONE) {
- if (mA2dpProxy != null) {
- mA2dpProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_UNDEFINED);
- }
- if (mHeadsetProxy != null) {
- mHeadsetProxy.setPriority(remoteDevice, BluetoothProfile.PRIORITY_UNDEFINED);
- }
- }
-
- if (mA2dpProxy == null || mHeadsetProxy == null) {
- Log.e(TAG, "Proxy is null:" + mA2dpProxy + ":" + mHeadsetProxy);
- }
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent == null) return;
-
- String action = intent.getAction();
- if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
- BluetoothDevice dev = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- String address = dev.getAddress();
- mPairingRequestRcvd.add(address);
- }
- }
- };
-}
diff --git a/core/java/android/server/BluetoothDeviceProperties.java b/core/java/android/server/BluetoothDeviceProperties.java
deleted file mode 100644
index fe3ef79..0000000
--- a/core/java/android/server/BluetoothDeviceProperties.java
+++ /dev/null
@@ -1,140 +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 android.server;
-
-import android.os.ParcelUuid;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-class BluetoothDeviceProperties {
-
- private static final String TAG = "BluetoothDeviceProperties";
-
- private final HashMap<String, Map<String, String>> mPropertiesMap;
- private final BluetoothService mService;
-
- BluetoothDeviceProperties(BluetoothService service) {
- mPropertiesMap = new HashMap<String, Map<String, String>>();
- mService = service;
- }
-
- Map<String, String> addProperties(String address, String[] properties) {
- /*
- * We get a DeviceFound signal every time RSSI changes or name changes.
- * Don't create a new Map object every time.
- */
- Map<String, String> propertyValues;
- synchronized(mPropertiesMap) {
- propertyValues = mPropertiesMap.get(address);
- if (propertyValues == null) {
- propertyValues = new HashMap<String, String>();
- }
-
- for (int i = 0; i < properties.length; i++) {
- String name = properties[i];
- String newValue = null;
- int len;
- if (name == null) {
- Log.e(TAG, "Error: Remote Device Property at index "
- + i + " is null");
- continue;
- }
- if (name.equals("UUIDs") || name.equals("Nodes")) {
- StringBuilder str = new StringBuilder();
- len = Integer.valueOf(properties[++i]);
- for (int j = 0; j < len; j++) {
- str.append(properties[++i]);
- str.append(",");
- }
- if (len > 0) {
- newValue = str.toString();
- }
- } else {
- newValue = properties[++i];
- }
-
- propertyValues.put(name, newValue);
- }
- mPropertiesMap.put(address, propertyValues);
- }
-
- // We have added a new remote device or updated its properties.
- // Also update the serviceChannel cache.
- mService.updateDeviceServiceChannelCache(address);
- return propertyValues;
- }
-
- void setProperty(String address, String name, String value) {
- synchronized(mPropertiesMap) {
- Map <String, String> propVal = mPropertiesMap.get(address);
- if (propVal != null) {
- propVal.put(name, value);
- mPropertiesMap.put(address, propVal);
- } else {
- Log.e(TAG, "setRemoteDeviceProperty for a device not in cache:" + address);
- }
- }
- }
-
- boolean isInCache(String address) {
- synchronized (mPropertiesMap) {
- return (mPropertiesMap.get(address) != null);
- }
- }
-
- boolean isEmpty() {
- synchronized (mPropertiesMap) {
- return mPropertiesMap.isEmpty();
- }
- }
-
- Set<String> keySet() {
- synchronized (mPropertiesMap) {
- return mPropertiesMap.keySet();
- }
- }
-
- String getProperty(String address, String property) {
- synchronized(mPropertiesMap) {
- Map<String, String> properties = mPropertiesMap.get(address);
- if (properties != null) {
- return properties.get(property);
- } else {
- // Query for remote device properties, again.
- // We will need to reload the cache when we switch Bluetooth on / off
- // or if we crash.
- properties = updateCache(address);
- if (properties != null) {
- return properties.get(property);
- }
- }
- }
- Log.e(TAG, "getRemoteDeviceProperty: " + property + " not present: " + address);
- return null;
- }
-
- Map<String, String> updateCache(String address) {
- String[] propValues = mService.getRemoteDeviceProperties(address);
- if (propValues != null) {
- return addProperties(address, propValues);
- }
- return null;
- }
-}
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
deleted file mode 100644
index b758e7f..0000000
--- a/core/java/android/server/BluetoothEventLoop.java
+++ /dev/null
@@ -1,1067 +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.server;
-
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHealth;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.PowerManager;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.List;
-
-
-/**
- * @hide
- */
-class BluetoothEventLoop {
- private static final String TAG = "BluetoothEventLoop";
- private static final boolean DBG = false;
-
- private int mNativeData;
- private Thread mThread;
- private boolean mStarted;
- private boolean mInterrupted;
-
- private final HashMap<String, Integer> mPasskeyAgentRequestData;
- private final HashMap<String, Integer> mAuthorizationAgentRequestData;
- private final BluetoothService mBluetoothService;
- private final BluetoothAdapter mAdapter;
- private final BluetoothAdapterStateMachine mBluetoothState;
- private BluetoothA2dp mA2dp;
- private final Context mContext;
- // The WakeLock is used for bringing up the LCD during a pairing request
- // from remote device when Android is in Suspend state.
- private PowerManager.WakeLock mWakeLock;
-
- private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 1;
- private static final int EVENT_AGENT_CANCEL = 2;
-
- private static final int CREATE_DEVICE_ALREADY_EXISTS = 1;
- private static final int CREATE_DEVICE_SUCCESS = 0;
- private static final int CREATE_DEVICE_FAILED = -1;
-
- private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
- private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- String address = null;
- switch (msg.what) {
- case EVENT_PAIRING_CONSENT_DELAYED_ACCEPT:
- address = (String)msg.obj;
- if (address != null) {
- mBluetoothService.setPairingConfirmation(address, true);
- }
- break;
- case EVENT_AGENT_CANCEL:
- // Set the Bond State to BOND_NONE.
- // We always have only 1 device in BONDING state.
- String[] devices = mBluetoothService.listInState(BluetoothDevice.BOND_BONDING);
- if (devices.length == 0) {
- break;
- } else if (devices.length > 1) {
- Log.e(TAG, " There is more than one device in the Bonding State");
- break;
- }
- address = devices[0];
- mBluetoothService.setBondState(address,
- BluetoothDevice.BOND_NONE,
- BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED);
- break;
- }
- }
- };
-
- static { classInitNative(); }
- private static native void classInitNative();
-
- /* package */ BluetoothEventLoop(Context context, BluetoothAdapter adapter,
- BluetoothService bluetoothService,
- BluetoothAdapterStateMachine bluetoothState) {
- mBluetoothService = bluetoothService;
- mContext = context;
- mBluetoothState = bluetoothState;
- mPasskeyAgentRequestData = new HashMap<String, Integer>();
- mAuthorizationAgentRequestData = new HashMap<String, Integer>();
- mAdapter = adapter;
- //WakeLock instantiation in BluetoothEventLoop class
- PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
- | PowerManager.ON_AFTER_RELEASE, TAG);
- mWakeLock.setReferenceCounted(false);
- initializeNativeDataNative();
- }
-
- /*package*/ void getProfileProxy() {
- mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP);
- mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.INPUT_DEVICE);
- }
-
- private BluetoothProfile.ServiceListener mProfileServiceListener =
- new BluetoothProfile.ServiceListener() {
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (profile == BluetoothProfile.A2DP) {
- mA2dp = (BluetoothA2dp) proxy;
- }
- }
- public void onServiceDisconnected(int profile) {
- if (profile == BluetoothProfile.A2DP) {
- mA2dp = null;
- }
- }
- };
-
-
- protected void finalize() throws Throwable {
- try {
- cleanupNativeDataNative();
- } finally {
- super.finalize();
- }
- }
-
- /* package */ HashMap<String, Integer> getPasskeyAgentRequestData() {
- return mPasskeyAgentRequestData;
- }
-
- /* package */ HashMap<String, Integer> getAuthorizationAgentRequestData() {
- return mAuthorizationAgentRequestData;
- }
-
- /* package */ void start() {
-
- if (!isEventLoopRunningNative()) {
- if (DBG) log("Starting Event Loop thread");
- startEventLoopNative();
- }
- }
-
- public void stop() {
- if (isEventLoopRunningNative()) {
- if (DBG) log("Stopping Event Loop thread");
- stopEventLoopNative();
- }
- }
-
- public boolean isEventLoopRunning() {
- return isEventLoopRunningNative();
- }
-
- private void addDevice(String address, String[] properties) {
- BluetoothDeviceProperties deviceProperties =
- mBluetoothService.getDeviceProperties();
- deviceProperties.addProperties(address, properties);
- String rssi = deviceProperties.getProperty(address, "RSSI");
- String classValue = deviceProperties.getProperty(address, "Class");
- String name = deviceProperties.getProperty(address, "Name");
- short rssiValue;
- // For incoming connections, we don't get the RSSI value. Use a default of MIN_VALUE.
- // If we accept the pairing, we will automatically show it at the top of the list.
- if (rssi != null) {
- rssiValue = (short)Integer.valueOf(rssi).intValue();
- } else {
- rssiValue = Short.MIN_VALUE;
- }
- if (classValue != null) {
- Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
- intent.putExtra(BluetoothDevice.EXTRA_CLASS,
- new BluetoothClass(Integer.valueOf(classValue)));
- intent.putExtra(BluetoothDevice.EXTRA_RSSI, rssiValue);
- intent.putExtra(BluetoothDevice.EXTRA_NAME, name);
-
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- } else {
- log ("ClassValue: " + classValue + " for remote device: " + address + " is null");
- }
- }
-
- /**
- * Called by native code on a DeviceFound signal from org.bluez.Adapter.
- *
- * @param address the MAC address of the new device
- * @param properties an array of property keys and value strings
- *
- * @see BluetoothDeviceProperties#addProperties(String, String[])
- */
- private void onDeviceFound(String address, String[] properties) {
- if (properties == null) {
- Log.e(TAG, "ERROR: Remote device properties are null");
- return;
- }
- addDevice(address, properties);
- }
-
- /**
- * Called by native code on a DeviceDisappeared signal from
- * org.bluez.Adapter.
- *
- * @param address the MAC address of the disappeared device
- */
- private void onDeviceDisappeared(String address) {
- Intent intent = new Intent(BluetoothDevice.ACTION_DISAPPEARED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- }
-
- /**
- * Called by native code on a DisconnectRequested signal from
- * org.bluez.Device.
- *
- * @param deviceObjectPath the object path for the disconnecting device
- */
- private void onDeviceDisconnectRequested(String deviceObjectPath) {
- String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
- if (address == null) {
- Log.e(TAG, "onDeviceDisconnectRequested: Address of the remote device in null");
- return;
- }
- Intent intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- }
-
- /**
- * Called by native code for the async response to a CreatePairedDevice
- * method call to org.bluez.Adapter.
- *
- * @param address the MAC address of the device to pair
- * @param result success or error result for the pairing operation
- */
- private void onCreatePairedDeviceResult(String address, int result) {
- address = address.toUpperCase();
- mBluetoothService.onCreatePairedDeviceResult(address, result);
- }
-
- /**
- * Called by native code on a DeviceCreated signal from org.bluez.Adapter.
- *
- * @param deviceObjectPath the object path for the created device
- */
- private void onDeviceCreated(String deviceObjectPath) {
- String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
- if (address == null) {
- Log.e(TAG, "onDeviceCreated: device address null!" + " deviceObjectPath: " +
- deviceObjectPath);
- return;
- }
- if (!mBluetoothService.isRemoteDeviceInCache(address)) {
- // Incoming connection, we haven't seen this device, add to cache.
- String[] properties = mBluetoothService.getRemoteDeviceProperties(address);
- if (properties != null) {
- addDevice(address, properties);
- }
- }
- }
-
- /**
- * Called by native code on a DeviceRemoved signal from org.bluez.Adapter.
- *
- * @param deviceObjectPath the object path for the removed device
- */
- private void onDeviceRemoved(String deviceObjectPath) {
- String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
- if (address != null) {
- mBluetoothService.setBondState(address.toUpperCase(), BluetoothDevice.BOND_NONE,
- BluetoothDevice.UNBOND_REASON_REMOVED);
- mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null);
- }
- }
-
- /**
- * Called by native code on a PropertyChanged signal from
- * org.bluez.Adapter. This method is also called from
- * {@link BluetoothAdapterStateMachine} to set the "Pairable"
- * property when Bluetooth is enabled.
- *
- * @param propValues a string array containing the key and one or more
- * values.
- */
- /*package*/ void onPropertyChanged(String[] propValues) {
- BluetoothAdapterProperties adapterProperties =
- mBluetoothService.getAdapterProperties();
-
- if (adapterProperties.isEmpty()) {
- // We have got a property change before
- // we filled up our cache.
- adapterProperties.getAllProperties();
- }
- log("Property Changed: " + propValues[0] + " : " + propValues[1]);
- String name = propValues[0];
- if (name.equals("Name")) {
- adapterProperties.setProperty(name, propValues[1]);
- Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
- intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- } else if (name.equals("Pairable") || name.equals("Discoverable")) {
- adapterProperties.setProperty(name, propValues[1]);
-
- if (name.equals("Discoverable")) {
- mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SCAN_MODE_CHANGED);
- }
-
- String pairable = name.equals("Pairable") ? propValues[1] :
- adapterProperties.getProperty("Pairable");
- String discoverable = name.equals("Discoverable") ? propValues[1] :
- adapterProperties.getProperty("Discoverable");
-
- // This shouldn't happen, unless Adapter Properties are null.
- if (pairable == null || discoverable == null)
- return;
-
- int mode = BluetoothService.bluezStringToScanMode(
- pairable.equals("true"),
- discoverable.equals("true"));
- if (mode >= 0) {
- Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
- intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, mode);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- }
- } else if (name.equals("Discovering")) {
- Intent intent;
- adapterProperties.setProperty(name, propValues[1]);
- if (propValues[1].equals("true")) {
- intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
- } else {
- // Stop the discovery.
- mBluetoothService.cancelDiscovery();
- intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
- }
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- } else if (name.equals("Devices") || name.equals("UUIDs")) {
- String value = null;
- int len = Integer.valueOf(propValues[1]);
- if (len > 0) {
- StringBuilder str = new StringBuilder();
- for (int i = 2; i < propValues.length; i++) {
- str.append(propValues[i]);
- str.append(",");
- }
- value = str.toString();
- }
- adapterProperties.setProperty(name, value);
- if (name.equals("UUIDs")) {
- mBluetoothService.updateBluetoothState(value);
- }
- } else if (name.equals("Powered")) {
- mBluetoothState.sendMessage(BluetoothAdapterStateMachine.POWER_STATE_CHANGED,
- propValues[1].equals("true") ? new Boolean(true) : new Boolean(false));
- } else if (name.equals("DiscoverableTimeout")) {
- adapterProperties.setProperty(name, propValues[1]);
- }
- }
-
- /**
- * Called by native code on a PropertyChanged signal from
- * org.bluez.Device.
- *
- * @param deviceObjectPath the object path for the changed device
- * @param propValues a string array containing the key and one or more
- * values.
- */
- private void onDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
- String name = propValues[0];
- String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
- if (address == null) {
- Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null");
- return;
- }
- log("Device property changed: " + address + " property: "
- + name + " value: " + propValues[1]);
-
- BluetoothDevice device = mAdapter.getRemoteDevice(address);
- if (name.equals("Name")) {
- mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
- Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- } else if (name.equals("Alias")) {
- mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
- Intent intent = new Intent(BluetoothDevice.ACTION_ALIAS_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- } else if (name.equals("Class")) {
- mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
- Intent intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothDevice.EXTRA_CLASS,
- new BluetoothClass(Integer.valueOf(propValues[1])));
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- } else if (name.equals("Connected")) {
- mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
- Intent intent = null;
- if (propValues[1].equals("true")) {
- intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
- // Set the link timeout to 8000 slots (5 sec timeout)
- // for bluetooth docks.
- if (mBluetoothService.isBluetoothDock(address)) {
- mBluetoothService.setLinkTimeout(address, 8000);
- }
- } else {
- intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
- }
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- } else if (name.equals("UUIDs")) {
- String uuid = null;
- int len = Integer.valueOf(propValues[1]);
- if (len > 0) {
- StringBuilder str = new StringBuilder();
- for (int i = 2; i < propValues.length; i++) {
- str.append(propValues[i]);
- str.append(",");
- }
- uuid = str.toString();
- }
- mBluetoothService.setRemoteDeviceProperty(address, name, uuid);
-
- // UUIDs have changed, query remote service channel and update cache.
- mBluetoothService.updateDeviceServiceChannelCache(address);
-
- mBluetoothService.sendUuidIntent(address);
- } else if (name.equals("Paired")) {
- if (propValues[1].equals("true")) {
- // If locally initiated pairing, we will
- // not go to BOND_BONDED state until we have received a
- // successful return value in onCreatePairedDeviceResult
- if (null == mBluetoothService.getPendingOutgoingBonding()) {
- mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDED);
- }
- } else {
- mBluetoothService.setBondState(address, BluetoothDevice.BOND_NONE);
- mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false");
- }
- } else if (name.equals("Trusted")) {
- if (DBG)
- log("set trust state succeeded, value is: " + propValues[1]);
- mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
- }
- }
-
- /**
- * Called by native code on a PropertyChanged signal from
- * org.bluez.Input.
- *
- * @param path the object path for the changed input device
- * @param propValues a string array containing the key and one or more
- * values.
- */
- private void onInputDevicePropertyChanged(String path, String[] propValues) {
- String address = mBluetoothService.getAddressFromObjectPath(path);
- if (address == null) {
- Log.e(TAG, "onInputDevicePropertyChanged: Address of the remote device is null");
- return;
- }
- log("Input Device : Name of Property is: " + propValues[0]);
- boolean state = false;
- if (propValues[1].equals("true")) {
- state = true;
- }
- mBluetoothService.handleInputDevicePropertyChange(address, state);
- }
-
- /**
- * Called by native code on a PropertyChanged signal from
- * org.bluez.Network.
- *
- * @param deviceObjectPath the object path for the changed PAN device
- * @param propValues a string array containing the key and one or more
- * values.
- */
- private void onPanDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
- String name = propValues[0];
- String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
- if (address == null) {
- Log.e(TAG, "onPanDevicePropertyChanged: Address of the remote device in null");
- return;
- }
- if (DBG) {
- log("Pan Device property changed: " + address + " property: "
- + name + " value: "+ propValues[1]);
- }
- BluetoothDevice device = mAdapter.getRemoteDevice(address);
- if (name.equals("Connected")) {
- if (propValues[1].equals("false")) {
- mBluetoothService.handlePanDeviceStateChange(device,
- BluetoothPan.STATE_DISCONNECTED,
- BluetoothPan.LOCAL_PANU_ROLE);
- }
- } else if (name.equals("Interface")) {
- String iface = propValues[1];
- if (!iface.equals("")) {
- mBluetoothService.handlePanDeviceStateChange(device, iface,
- BluetoothPan.STATE_CONNECTED,
- BluetoothPan.LOCAL_PANU_ROLE);
- }
- }
- }
-
- private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) {
- String address = mBluetoothService.getAddressFromObjectPath(objectPath);
- if (address == null) {
- Log.e(TAG, "Unable to get device address in checkPairingRequestAndGetAddress, " +
- "returning null");
- return null;
- }
- address = address.toUpperCase();
- mPasskeyAgentRequestData.put(address, new Integer(nativeData));
-
- if (mBluetoothService.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) {
- // shutdown path
- mBluetoothService.cancelPairingUserInput(address);
- return null;
- }
- // Set state to BONDING. For incoming connections it will be set here.
- // For outgoing connections, it gets set when we call createBond.
- // Also set it only when the state is not already Bonded, we can sometimes
- // get an authorization request from the remote end if it doesn't have the link key
- // while we still have it.
- if (mBluetoothService.getBondState(address) != BluetoothDevice.BOND_BONDED)
- mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDING);
- return address;
- }
-
- /**
- * Called by native code on a RequestPairingConsent method call to
- * org.bluez.Agent.
- *
- * @param objectPath the path of the device to request pairing consent for
- * @param nativeData a native pointer to the original D-Bus message
- */
- private void onRequestPairingConsent(String objectPath, int nativeData) {
- String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
- if (address == null) return;
-
- /* The link key will not be stored if the incoming request has MITM
- * protection switched on. Unfortunately, some devices have MITM
- * switched on even though their capabilities are NoInputNoOutput,
- * so we may get this request many times. Also if we respond immediately,
- * the other end is unable to handle it. Delay sending the message.
- */
- if (mBluetoothService.getBondState(address) == BluetoothDevice.BOND_BONDED) {
- Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT);
- message.obj = address;
- mHandler.sendMessageDelayed(message, 1500);
- return;
- }
- // Acquire wakelock during PIN code request to bring up LCD display
- mWakeLock.acquire();
- Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
- intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
- BluetoothDevice.PAIRING_VARIANT_CONSENT);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- // Release wakelock to allow the LCD to go off after the PIN popup notification.
- mWakeLock.release();
- return;
- }
-
- /**
- * Called by native code on a RequestConfirmation method call to
- * org.bluez.Agent.
- *
- * @param objectPath the path of the device to confirm the passkey for
- * @param passkey an integer containing the 6-digit passkey to confirm
- * @param nativeData a native pointer to the original D-Bus message
- */
- private void onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData) {
- String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
- if (address == null) return;
- // Acquire wakelock during PIN code request to bring up LCD display
- mWakeLock.acquire();
- Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
- intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, passkey);
- intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
- BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- // Release wakelock to allow the LCD to go off after the PIN popup notification.
- mWakeLock.release();
- return;
- }
-
- /**
- * Called by native code on a RequestPasskey method call to
- * org.bluez.Agent.
- *
- * @param objectPath the path of the device requesting a passkey
- * @param nativeData a native pointer to the original D-Bus message
- */
- private void onRequestPasskey(String objectPath, int nativeData) {
- String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
- if (address == null) return;
- // Acquire wakelock during PIN code request to bring up LCD display
- mWakeLock.acquire();
- Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
- intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
- BluetoothDevice.PAIRING_VARIANT_PASSKEY);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- // Release wakelock to allow the LCD to go off after the PIN popup notification.
- mWakeLock.release();
- return;
- }
-
- /**
- * Called by native code on a RequestPinCode method call to
- * org.bluez.Agent.
- *
- * @param objectPath the path of the device requesting a PIN code
- * @param nativeData a native pointer to the original D-Bus message
- */
- private void onRequestPinCode(String objectPath, int nativeData) {
- String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
- if (address == null) return;
-
- String pendingOutgoingAddress =
- mBluetoothService.getPendingOutgoingBonding();
- BluetoothClass btClass = new BluetoothClass(mBluetoothService.getRemoteClass(address));
- int btDeviceClass = btClass.getDeviceClass();
-
- if (address.equals(pendingOutgoingAddress)) {
- // we initiated the bonding
-
- // Check if its a dock
- if (mBluetoothService.isBluetoothDock(address)) {
- String pin = mBluetoothService.getDockPin();
- mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes(pin));
- return;
- }
-
- // try 0000 once if the device looks dumb
- switch (btDeviceClass) {
- case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
- case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
- case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
- case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
- case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
- if (mBluetoothService.attemptAutoPair(address)) return;
- }
- }
-
- if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD ||
- btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) {
- // Its a keyboard. Follow the HID spec recommendation of creating the
- // passkey and displaying it to the user. If the keyboard doesn't follow
- // the spec recommendation, check if the keyboard has a fixed PIN zero
- // and pair.
- if (mBluetoothService.isFixedPinZerosAutoPairKeyboard(address)) {
- mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
- return;
- }
-
- // Generate a variable PIN. This is not truly random but good enough.
- int pin = (int) Math.floor(Math.random() * 10000);
- sendDisplayPinIntent(address, pin);
- return;
- }
- // Acquire wakelock during PIN code request to bring up LCD display
- mWakeLock.acquire();
- Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
- intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- // Release wakelock to allow the LCD to go off after the PIN popup notification.
- mWakeLock.release();
- return;
- }
-
- /**
- * Called by native code on a DisplayPasskey method call to
- * org.bluez.Agent.
- *
- * @param objectPath the path of the device to display the passkey for
- * @param passkey an integer containing the 6-digit passkey
- * @param nativeData a native pointer to the original D-Bus message
- */
- private void onDisplayPasskey(String objectPath, int passkey, int nativeData) {
- String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
- if (address == null) return;
-
- // Acquire wakelock during PIN code request to bring up LCD display
- mWakeLock.acquire();
- Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
- intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, passkey);
- intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
- BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- //Release wakelock to allow the LCD to go off after the PIN popup notification.
- mWakeLock.release();
- }
-
- private void sendDisplayPinIntent(String address, int pin) {
- // Acquire wakelock during PIN code request to bring up LCD display
- mWakeLock.acquire();
- Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
- intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin);
- intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
- BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- //Release wakelock to allow the LCD to go off after the PIN popup notifcation.
- mWakeLock.release();
- }
-
- /**
- * Called by native code on a RequestOobData method call to
- * org.bluez.Agent.
- *
- * @param objectPath the path of the device requesting OOB data
- * @param nativeData a native pointer to the original D-Bus message
- */
- private void onRequestOobData(String objectPath, int nativeData) {
- String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
- if (address == null) return;
-
- Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
- intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
- BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- }
-
- /**
- * Called by native code on an Authorize method call to org.bluez.Agent.
- *
- * @param objectPath the path of the device requesting to be authorized
- * @param deviceUuid the UUID of the requesting device
- * @param nativeData reference for native data
- */
- private void onAgentAuthorize(String objectPath, String deviceUuid, int nativeData) {
- if (!mBluetoothService.isEnabled()) return;
-
- String address = mBluetoothService.getAddressFromObjectPath(objectPath);
- if (address == null) {
- Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize");
- return;
- }
-
- boolean authorized = false;
- ParcelUuid uuid = ParcelUuid.fromString(deviceUuid);
-
- BluetoothDevice device = mAdapter.getRemoteDevice(address);
- mAuthorizationAgentRequestData.put(address, new Integer(nativeData));
-
- // Bluez sends the UUID of the local service being accessed, _not_ the
- // remote service
- if (mA2dp != null &&
- (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid)
- || BluetoothUuid.isAdvAudioDist(uuid)) &&
- !isOtherSinkInNonDisconnectedState(address)) {
- authorized = mA2dp.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
- if (authorized && !BluetoothUuid.isAvrcpTarget(uuid)) {
- Log.i(TAG, "First check pass for incoming A2DP / AVRCP connection from " + address);
- // Some headsets try to connect AVCTP before AVDTP - against the recommendation
- // If AVCTP connection fails, we get stuck in IncomingA2DP state in the state
- // machine. We don't handle AVCTP signals currently. We only send
- // intents for AVDTP state changes. We need to handle both of them in
- // some cases. For now, just don't move to incoming state in this case.
- mBluetoothService.notifyIncomingA2dpConnection(address, false);
- } else {
- Log.i(TAG, "" + authorized +
- "Incoming A2DP / AVRCP connection from " + address);
- mA2dp.allowIncomingConnect(device, authorized);
- mBluetoothService.notifyIncomingA2dpConnection(address, true);
- }
- } else if (BluetoothUuid.isInputDevice(uuid)) {
- // We can have more than 1 input device connected.
- authorized = mBluetoothService.getInputDevicePriority(device) >
- BluetoothInputDevice.PRIORITY_OFF;
- if (authorized) {
- Log.i(TAG, "First check pass for incoming HID connection from " + address);
- // notify profile state change
- mBluetoothService.notifyIncomingHidConnection(address);
- } else {
- Log.i(TAG, "Rejecting incoming HID connection from " + address);
- mBluetoothService.allowIncomingProfileConnect(device, authorized);
- }
- } else if (BluetoothUuid.isBnep(uuid)) {
- // PAN doesn't go to the state machine, accept or reject from here
- authorized = mBluetoothService.allowIncomingTethering();
- mBluetoothService.allowIncomingProfileConnect(device, authorized);
- } else {
- Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address);
- mBluetoothService.allowIncomingProfileConnect(device, authorized);
- }
- log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized);
- }
-
- private boolean onAgentOutOfBandDataAvailable(String objectPath) {
- if (!mBluetoothService.isEnabled()) return false;
-
- String address = mBluetoothService.getAddressFromObjectPath(objectPath);
- if (address == null) return false;
-
- if (mBluetoothService.getDeviceOutOfBandData(
- mAdapter.getRemoteDevice(address)) != null) {
- return true;
- }
- return false;
- }
-
- private boolean isOtherSinkInNonDisconnectedState(String address) {
- List<BluetoothDevice> devices =
- mA2dp.getDevicesMatchingConnectionStates(new int[] {BluetoothA2dp.STATE_CONNECTED,
- BluetoothA2dp.STATE_CONNECTING,
- BluetoothA2dp.STATE_DISCONNECTING});
-
- if (devices.size() == 0) return false;
- for (BluetoothDevice dev: devices) {
- if (!dev.getAddress().equals(address)) return true;
- }
- return false;
- }
-
- /**
- * Called by native code on a Cancel method call to org.bluez.Agent.
- */
- private void onAgentCancel() {
- Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
-
- mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_AGENT_CANCEL),
- 1500);
-
- return;
- }
-
- /**
- * Called by native code for the async response to a DiscoverServices
- * method call to org.bluez.Adapter.
- *
- * @param deviceObjectPath the path for the specified device
- * @param result true for success; false on error
- */
- private void onDiscoverServicesResult(String deviceObjectPath, boolean result) {
- String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
- if (address == null) return;
-
- // We don't parse the xml here, instead just query Bluez for the properties.
- if (result) {
- mBluetoothService.updateRemoteDevicePropertiesCache(address);
- }
- mBluetoothService.sendUuidIntent(address);
- mBluetoothService.makeServiceChannelCallbacks(address);
- }
-
- /**
- * Called by native code for the async response to a CreateDevice
- * method call to org.bluez.Adapter.
- *
- * @param address the MAC address of the device to create
- * @param result {@link #CREATE_DEVICE_SUCCESS},
- * {@link #CREATE_DEVICE_ALREADY_EXISTS} or {@link #CREATE_DEVICE_FAILED}}
- */
- private void onCreateDeviceResult(String address, int result) {
- if (DBG) log("Result of onCreateDeviceResult:" + result);
-
- switch (result) {
- case CREATE_DEVICE_ALREADY_EXISTS:
- String path = mBluetoothService.getObjectPathFromAddress(address);
- if (path != null) {
- mBluetoothService.discoverServicesNative(path, "");
- break;
- }
- Log.w(TAG, "Device exists, but we don't have the bluez path, failing");
- // fall-through
- case CREATE_DEVICE_FAILED:
- mBluetoothService.sendUuidIntent(address);
- mBluetoothService.makeServiceChannelCallbacks(address);
- break;
- case CREATE_DEVICE_SUCCESS:
- // nothing to do, UUID intent's will be sent via property changed
- }
- }
-
- /**
- * Called by native code for the async response to a Connect
- * method call to org.bluez.Input.
- *
- * @param path the path of the specified input device
- * @param result Result code of the operation.
- */
- private void onInputDeviceConnectionResult(String path, int result) {
- // Success case gets handled by Property Change signal
- if (result != BluetoothInputDevice.INPUT_OPERATION_SUCCESS) {
- String address = mBluetoothService.getAddressFromObjectPath(path);
- if (address == null) return;
-
- boolean connected = false;
- BluetoothDevice device = mAdapter.getRemoteDevice(address);
- int state = mBluetoothService.getInputDeviceConnectionState(device);
- if (state == BluetoothInputDevice.STATE_CONNECTING) {
- if (result == BluetoothInputDevice.INPUT_CONNECT_FAILED_ALREADY_CONNECTED) {
- connected = true;
- } else {
- connected = false;
- }
- } else if (state == BluetoothInputDevice.STATE_DISCONNECTING) {
- if (result == BluetoothInputDevice.INPUT_DISCONNECT_FAILED_NOT_CONNECTED) {
- connected = false;
- } else {
- // There is no better way to handle this, this shouldn't happen
- connected = true;
- }
- } else {
- Log.e(TAG, "Error onInputDeviceConnectionResult. State is:" + state);
- }
- mBluetoothService.handleInputDevicePropertyChange(address, connected);
- }
- }
-
- /**
- * Called by native code for the async response to a Connect
- * method call to org.bluez.Network.
- *
- * @param path the path of the specified PAN device
- * @param result Result code of the operation.
- */
- private void onPanDeviceConnectionResult(String path, int result) {
- log ("onPanDeviceConnectionResult " + path + " " + result);
- // Success case gets handled by Property Change signal
- if (result != BluetoothPan.PAN_OPERATION_SUCCESS) {
- String address = mBluetoothService.getAddressFromObjectPath(path);
- if (address == null) return;
-
- boolean connected = false;
- BluetoothDevice device = mAdapter.getRemoteDevice(address);
- int state = mBluetoothService.getPanDeviceConnectionState(device);
- if (state == BluetoothPan.STATE_CONNECTING) {
- if (result == BluetoothPan.PAN_CONNECT_FAILED_ALREADY_CONNECTED) {
- connected = true;
- } else {
- connected = false;
- }
- } else if (state == BluetoothPan.STATE_DISCONNECTING) {
- if (result == BluetoothPan.PAN_DISCONNECT_FAILED_NOT_CONNECTED) {
- connected = false;
- } else {
- // There is no better way to handle this, this shouldn't happen
- connected = true;
- }
- } else {
- Log.e(TAG, "Error onPanDeviceConnectionResult. State is: "
- + state + " result: "+ result);
- }
- int newState = connected? BluetoothPan.STATE_CONNECTED :
- BluetoothPan.STATE_DISCONNECTED;
- mBluetoothService.handlePanDeviceStateChange(device, newState,
- BluetoothPan.LOCAL_PANU_ROLE);
- }
- }
-
- /**
- * Called by native code for the async response to a Connect
- * method call to org.bluez.Health
- *
- * @param chanCode The internal id of the channel
- * @param result Result code of the operation.
- */
- private void onHealthDeviceConnectionResult(int chanCode, int result) {
- log ("onHealthDeviceConnectionResult " + chanCode + " " + result);
- // Success case gets handled by Property Change signal
- if (result != BluetoothHealth.HEALTH_OPERATION_SUCCESS) {
- mBluetoothService.onHealthDeviceChannelConnectionError(chanCode,
- BluetoothHealth.STATE_CHANNEL_DISCONNECTED);
- }
- }
-
- /**
- * Called by native code on a DeviceDisconnected signal from
- * org.bluez.NetworkServer.
- *
- * @param address the MAC address of the disconnected device
- */
- private void onNetworkDeviceDisconnected(String address) {
- BluetoothDevice device = mAdapter.getRemoteDevice(address);
- mBluetoothService.handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTED,
- BluetoothPan.LOCAL_NAP_ROLE);
- }
-
- /**
- * Called by native code on a DeviceConnected signal from
- * org.bluez.NetworkServer.
- *
- * @param address the MAC address of the connected device
- * @param iface interface of remote network
- * @param destUuid unused UUID parameter
- */
- private void onNetworkDeviceConnected(String address, String iface, int destUuid) {
- BluetoothDevice device = mAdapter.getRemoteDevice(address);
- mBluetoothService.handlePanDeviceStateChange(device, iface, BluetoothPan.STATE_CONNECTED,
- BluetoothPan.LOCAL_NAP_ROLE);
- }
-
- /**
- * Called by native code on a PropertyChanged signal from
- * org.bluez.HealthDevice.
- *
- * @param devicePath the object path of the remote device
- * @param propValues Properties (Name-Value) of the Health Device.
- */
- private void onHealthDevicePropertyChanged(String devicePath, String[] propValues) {
- log("Health Device : Name of Property is: " + propValues[0] + " Value:" + propValues[1]);
- mBluetoothService.onHealthDevicePropertyChanged(devicePath, propValues[1]);
- }
-
- /**
- * Called by native code on a ChannelCreated/Deleted signal from
- * org.bluez.HealthDevice.
- *
- * @param devicePath the object path of the remote device
- * @param channelPath the path of the health channel.
- * @param exists Boolean to indicate if the channel was created or deleted.
- */
- private void onHealthDeviceChannelChanged(String devicePath, String channelPath,
- boolean exists) {
- log("Health Device : devicePath: " + devicePath + ":channelPath:" + channelPath +
- ":exists" + exists);
- mBluetoothService.onHealthDeviceChannelChanged(devicePath, channelPath, exists);
- }
-
- private static void log(String msg) {
- Log.d(TAG, msg);
- }
-
- private native void initializeNativeDataNative();
- private native void startEventLoopNative();
- private native void stopEventLoopNative();
- private native boolean isEventLoopRunningNative();
- private native void cleanupNativeDataNative();
-}
diff --git a/core/java/android/server/BluetoothHealthProfileHandler.java b/core/java/android/server/BluetoothHealthProfileHandler.java
deleted file mode 100644
index 5e93b81..0000000
--- a/core/java/android/server/BluetoothHealthProfileHandler.java
+++ /dev/null
@@ -1,671 +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.server;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHealth;
-import android.bluetooth.BluetoothHealthAppConfiguration;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.IBluetoothHealthCallback;
-import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * This handles all the operations on the Bluetooth Health profile.
- * All functions are called by BluetoothService, as Bluetooth Service
- * is the Service handler for the HDP profile.
- *
- * @hide
- */
-final class BluetoothHealthProfileHandler {
- private static final String TAG = "BluetoothHealthProfileHandler";
- private static final boolean DBG = false;
-
- private static BluetoothHealthProfileHandler sInstance;
- private BluetoothService mBluetoothService;
- private ArrayList<HealthChannel> mHealthChannels;
- private HashMap <BluetoothHealthAppConfiguration, String> mHealthAppConfigs;
- private HashMap <BluetoothDevice, Integer> mHealthDevices;
- private HashMap <BluetoothHealthAppConfiguration, IBluetoothHealthCallback> mCallbacks;
-
- private static final int MESSAGE_REGISTER_APPLICATION = 0;
- private static final int MESSAGE_UNREGISTER_APPLICATION = 1;
- private static final int MESSAGE_CONNECT_CHANNEL = 2;
- private static final AtomicInteger sChannelId = new AtomicInteger();
-
- class HealthChannel {
- private ParcelFileDescriptor mChannelFd;
- private boolean mMainChannel;
- private String mChannelPath;
- private BluetoothDevice mDevice;
- private BluetoothHealthAppConfiguration mConfig;
- private int mState;
- private int mChannelType;
- private int mId;
-
- HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config,
- ParcelFileDescriptor fd, boolean mainChannel, String channelPath) {
- mChannelFd = fd;
- mMainChannel = mainChannel;
- mChannelPath = channelPath;
- mDevice = device;
- mConfig = config;
- mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
- mId = getChannelId();
- }
- }
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_REGISTER_APPLICATION:
- BluetoothHealthAppConfiguration registerApp =
- (BluetoothHealthAppConfiguration) msg.obj;
- int role = registerApp.getRole();
- String path = null;
-
- if (role == BluetoothHealth.SINK_ROLE) {
- path = mBluetoothService.registerHealthApplicationNative(
- registerApp.getDataType(), getStringRole(role), registerApp.getName());
- } else {
- path = mBluetoothService.registerHealthApplicationNative(
- registerApp.getDataType(), getStringRole(role), registerApp.getName(),
- getStringChannelType(registerApp.getChannelType()));
- }
-
- if (path == null) {
- callHealthApplicationStatusCallback(registerApp,
- BluetoothHealth.APP_CONFIG_REGISTRATION_FAILURE);
- mCallbacks.remove(registerApp);
- } else {
- mHealthAppConfigs.put(registerApp, path);
- callHealthApplicationStatusCallback(registerApp,
- BluetoothHealth.APP_CONFIG_REGISTRATION_SUCCESS);
- }
-
- break;
- case MESSAGE_UNREGISTER_APPLICATION:
- BluetoothHealthAppConfiguration unregisterApp =
- (BluetoothHealthAppConfiguration) msg.obj;
-
- // Disconnect all the channels
- for (HealthChannel chan : mHealthChannels) {
- if (chan.mConfig.equals(unregisterApp) &&
- chan.mState != BluetoothHealth.STATE_CHANNEL_DISCONNECTED) {
- disconnectChannel(chan.mDevice, unregisterApp, chan.mId);
- }
- }
-
- boolean result = mBluetoothService.unregisterHealthApplicationNative(
- mHealthAppConfigs.get(unregisterApp));
- if (result) {
- callHealthApplicationStatusCallback(unregisterApp,
- BluetoothHealth.APP_CONFIG_UNREGISTRATION_SUCCESS);
- mCallbacks.remove(unregisterApp);
- mHealthAppConfigs.remove(unregisterApp);
- } else {
- callHealthApplicationStatusCallback(unregisterApp,
- BluetoothHealth.APP_CONFIG_UNREGISTRATION_FAILURE);
- }
- break;
- case MESSAGE_CONNECT_CHANNEL:
- HealthChannel chan = (HealthChannel)msg.obj;
- String deviceObjectPath =
- mBluetoothService.getObjectPathFromAddress(chan.mDevice.getAddress());
- String configPath = mHealthAppConfigs.get(chan.mConfig);
- String channelType = getStringChannelType(chan.mChannelType);
-
- if (!mBluetoothService.createChannelNative(deviceObjectPath, configPath,
- channelType, chan.mId)) {
- int prevState = chan.mState;
- int state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
- callHealthChannelCallback(chan.mConfig, chan.mDevice, prevState, state, null,
- chan.mId);
- mHealthChannels.remove(chan);
- }
- }
- }
- };
-
- private BluetoothHealthProfileHandler(Context context, BluetoothService service) {
- mBluetoothService = service;
- mHealthAppConfigs = new HashMap<BluetoothHealthAppConfiguration, String>();
- mHealthChannels = new ArrayList<HealthChannel>();
- mHealthDevices = new HashMap<BluetoothDevice, Integer>();
- mCallbacks = new HashMap<BluetoothHealthAppConfiguration, IBluetoothHealthCallback>();
- }
-
- static synchronized BluetoothHealthProfileHandler getInstance(Context context,
- BluetoothService service) {
- if (sInstance == null) sInstance = new BluetoothHealthProfileHandler(context, service);
- return sInstance;
- }
-
- boolean registerAppConfiguration(BluetoothHealthAppConfiguration config,
- IBluetoothHealthCallback callback) {
- Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION);
- msg.obj = config;
- mHandler.sendMessage(msg);
- mCallbacks.put(config, callback);
- return true;
- }
-
- boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
- String path = mHealthAppConfigs.get(config);
- if (path == null) return false;
-
- Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION);
- msg.obj = config;
- mHandler.sendMessage(msg);
- return true;
- }
-
- boolean connectChannelToSource(BluetoothDevice device,
- BluetoothHealthAppConfiguration config) {
- return connectChannel(device, config, BluetoothHealth.CHANNEL_TYPE_ANY);
- }
-
- private HealthChannel getMainChannel(BluetoothDevice device,
- BluetoothHealthAppConfiguration config) {
- for (HealthChannel chan: mHealthChannels) {
- if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) {
- if (chan.mMainChannel) return chan;
- }
- }
- return null;
- }
-
- boolean connectChannel(BluetoothDevice device,
- BluetoothHealthAppConfiguration config, int channelType) {
- String deviceObjectPath =
- mBluetoothService.getObjectPathFromAddress(device.getAddress());
- if (deviceObjectPath == null) return false;
-
- String configPath = mHealthAppConfigs.get(config);
- if (configPath == null) return false;
-
- HealthChannel chan = new HealthChannel(device, config, null, false, null);
- chan.mState = BluetoothHealth.STATE_CHANNEL_CONNECTING;
- chan.mChannelType = channelType;
- mHealthChannels.add(chan);
-
- int prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
- int state = BluetoothHealth.STATE_CHANNEL_CONNECTING;
- callHealthChannelCallback(config, device, prevState, state, null, chan.mId);
-
- Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_CHANNEL);
- msg.obj = chan;
- mHandler.sendMessage(msg);
-
- return true;
- }
-
- private String getStringChannelType(int type) {
- if (type == BluetoothHealth.CHANNEL_TYPE_RELIABLE) {
- return "Reliable";
- } else if (type == BluetoothHealth.CHANNEL_TYPE_STREAMING) {
- return "Streaming";
- } else {
- return "Any";
- }
- }
-
- private String getStringRole(int role) {
- if (role == BluetoothHealth.SINK_ROLE) {
- return "Sink";
- } else if (role == BluetoothHealth.SOURCE_ROLE) {
- return "Streaming";
- } else {
- return null;
- }
- }
-
- private int getChannelId() {
- // The function doesn't need to be synchronized, as the health profile handler
- // will only allow one health channel object creation at a time.
- // In the worst case the while loop will have to break out at some point of
- // time, because only a limited number of L2CAP channels are possible.
- int id;
- boolean found;
- do {
- id = sChannelId.incrementAndGet();
- found = false;
- for (HealthChannel chan: mHealthChannels) {
- if (chan.mId == id) found = true;
- }
- } while (found);
- return id;
- }
-
- boolean disconnectChannel(BluetoothDevice device,
- BluetoothHealthAppConfiguration config, int id) {
- HealthChannel chan = findChannelById(id);
- if (chan == null) {
- return false;
- }
-
- String deviceObjectPath =
- mBluetoothService.getObjectPathFromAddress(device.getAddress());
-
- mBluetoothService.releaseChannelFdNative(chan.mChannelPath);
-
- int prevState = chan.mState;
- chan.mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTING;
- callHealthChannelCallback(config, device, prevState, chan.mState,
- null, chan.mId);
-
- if (!mBluetoothService.destroyChannelNative(deviceObjectPath, chan.mChannelPath,
- chan.mId)) {
- prevState = chan.mState;
- chan.mState = BluetoothHealth.STATE_CHANNEL_CONNECTED;
- callHealthChannelCallback(config, device, prevState, chan.mState,
- chan.mChannelFd, chan.mId);
- return false;
- } else {
- return true;
- }
- }
-
- private HealthChannel findChannelById(int id) {
- for (HealthChannel chan : mHealthChannels) {
- if (chan.mId == id) return chan;
- }
- return null;
- }
-
- private HealthChannel findChannelByPath(BluetoothDevice device, String path) {
- for (HealthChannel chan : mHealthChannels) {
- if (path.equals(chan.mChannelPath) && device.equals(chan.mDevice)) return chan;
- }
- return null;
- }
-
- private List<HealthChannel> findChannelByStates(BluetoothDevice device, int[] states) {
- List<HealthChannel> channels = new ArrayList<HealthChannel>();
- for (HealthChannel chan: mHealthChannels) {
- if (chan.mDevice.equals(device)) {
- for (int state : states) {
- if (chan.mState == state) {
- channels.add(chan);
- }
- }
- }
- }
- return channels;
- }
-
- private HealthChannel findConnectingChannel(BluetoothDevice device,
- BluetoothHealthAppConfiguration config) {
- for (HealthChannel chan : mHealthChannels) {
- if (chan.mDevice.equals(device) && chan.mConfig.equals(config) &&
- chan.mState == BluetoothHealth.STATE_CHANNEL_CONNECTING) return chan;
- }
- return null;
- }
-
- ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
- BluetoothHealthAppConfiguration config) {
- HealthChannel chan = getMainChannel(device, config);
- if (chan != null) {
- ParcelFileDescriptor pfd = null;
- try {
- pfd = chan.mChannelFd.dup();
- return pfd;
- } catch (IOException e) {
- return null;
- }
- }
-
- String objectPath =
- mBluetoothService.getObjectPathFromAddress(device.getAddress());
- if (objectPath == null) return null;
-
- String mainChannelPath = mBluetoothService.getMainChannelNative(objectPath);
- if (mainChannelPath == null) return null;
-
- // We had no record of the main channel but querying Bluez we got a
- // main channel. We might not have received the PropertyChanged yet for
- // the main channel creation so update our data structure here.
- chan = findChannelByPath(device, mainChannelPath);
- if (chan == null) {
- errorLog("Main Channel present but we don't have any account of it:" +
- device +":" + config);
- return null;
- }
- chan.mMainChannel = true;
- try {
- return chan.mChannelFd.dup();
- } catch (IOException e) {
- return null;
- }
- }
-
- /*package*/ void onHealthDevicePropertyChanged(String devicePath,
- String channelPath) {
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- String address = mBluetoothService.getAddressFromObjectPath(devicePath);
- if (address == null) return;
-
- //TODO: Fix this in Bluez
- if (channelPath.equals("/")) {
- // This means that the main channel is being destroyed.
- return;
- }
-
- BluetoothDevice device = adapter.getRemoteDevice(address);
- BluetoothHealthAppConfiguration config = findHealthApplication(device,
- channelPath);
- if (config != null) {
- HealthChannel chan = findChannelByPath(device, channelPath);
- if (chan == null) {
- errorLog("Health Channel is not present:" + channelPath);
- } else {
- chan.mMainChannel = true;
- }
- }
- }
-
- /*package*/ void onHealthDeviceChannelConnectionError(int chanCode,
- int state) {
- HealthChannel channel = findChannelById(chanCode);
- if (channel == null) errorLog("No record of this channel:" + chanCode);
-
- callHealthChannelCallback(channel.mConfig, channel.mDevice, channel.mState, state, null,
- chanCode);
- }
-
- private BluetoothHealthAppConfiguration findHealthApplication(
- BluetoothDevice device, String channelPath) {
- BluetoothHealthAppConfiguration config = null;
- HealthChannel chan = findChannelByPath(device, channelPath);
-
- if (chan != null) {
- config = chan.mConfig;
- } else {
- String configPath = mBluetoothService.getChannelApplicationNative(channelPath);
- if (configPath == null) {
- errorLog("Config path is null for application");
- } else {
- for (Entry<BluetoothHealthAppConfiguration, String> e :
- mHealthAppConfigs.entrySet()) {
- if (e.getValue().equals(configPath)) {
- config = e.getKey();
- }
- }
- if (config == null) errorLog("No associated application for path:" + configPath);
- }
- }
- return config;
- }
-
- /*package*/ void onHealthDeviceChannelChanged(String devicePath,
- String channelPath, boolean exists) {
- debugLog("onHealthDeviceChannelChanged: devicePath: " + devicePath +
- "ChannelPath: " + channelPath + "Exists: " + exists);
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- String address = mBluetoothService.getAddressFromObjectPath(devicePath);
- if (address == null) return;
-
- BluetoothDevice device = adapter.getRemoteDevice(address);
- BluetoothHealthAppConfiguration config;
- int state, prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
- ParcelFileDescriptor fd;
- HealthChannel channel;
- config = findHealthApplication(device, channelPath);
-
- if (exists) {
- channel = findConnectingChannel(device, config);
- if (channel == null) {
- channel = new HealthChannel(device, config, null, false,
- channelPath);
- channel.mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
- mHealthChannels.add(channel);
- }
- channel.mChannelPath = channelPath;
-
- fd = mBluetoothService.getChannelFdNative(channelPath);
- if (fd == null) {
- errorLog("Error obtaining fd for channel:" + channelPath);
- disconnectChannel(device, config, channel.mId);
- return;
- }
- boolean mainChannel =
- getMainChannel(device, config) == null ? false : true;
- if (!mainChannel) {
- String mainChannelPath =
- mBluetoothService.getMainChannelNative(devicePath);
- if (mainChannelPath == null) {
- errorLog("Main Channel Path is null for devicePath:" + devicePath);
- return;
- }
- if (mainChannelPath.equals(channelPath)) mainChannel = true;
- }
-
- channel.mChannelFd = fd;
- channel.mMainChannel = mainChannel;
- prevState = channel.mState;
- state = BluetoothHealth.STATE_CHANNEL_CONNECTED;
- } else {
- channel = findChannelByPath(device, channelPath);
- if (channel == null) {
- errorLog("Channel not found:" + config + ":" + channelPath);
- return;
- }
- mHealthChannels.remove(channel);
-
- channel.mChannelFd = null;
- prevState = channel.mState;
- state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
- }
- channel.mState = state;
- callHealthChannelCallback(config, device, prevState, state, channel.mChannelFd,
- channel.mId);
- }
-
- private void callHealthChannelCallback(BluetoothHealthAppConfiguration config,
- BluetoothDevice device, int prevState, int state, ParcelFileDescriptor fd, int id) {
- broadcastHealthDeviceStateChange(device, prevState, state);
-
- debugLog("Health Device Callback: " + device + " State Change: "
- + prevState + "->" + state);
-
- ParcelFileDescriptor dupedFd = null;
- if (fd != null) {
- try {
- dupedFd = fd.dup();
- } catch (IOException e) {
- dupedFd = null;
- errorLog("Exception while duping: " + e);
- }
- }
-
- IBluetoothHealthCallback callback = mCallbacks.get(config);
- if (callback != null) {
- try {
- callback.onHealthChannelStateChange(config, device, prevState, state, dupedFd, id);
- } catch (RemoteException e) {
- errorLog("Remote Exception:" + e);
- }
- }
- }
-
- private void callHealthApplicationStatusCallback(
- BluetoothHealthAppConfiguration config, int status) {
- debugLog("Health Device Application: " + config + " State Change: status:"
- + status);
- IBluetoothHealthCallback callback = mCallbacks.get(config);
- if (callback != null) {
- try {
- callback.onHealthAppConfigurationStatusChange(config, status);
- } catch (RemoteException e) {
- errorLog("Remote Exception:" + e);
- }
- }
- }
-
- int getHealthDeviceConnectionState(BluetoothDevice device) {
- if (mHealthDevices.get(device) == null) {
- return BluetoothHealth.STATE_DISCONNECTED;
- }
- return mHealthDevices.get(device);
- }
-
- List<BluetoothDevice> getConnectedHealthDevices() {
- List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(
- new int[] {BluetoothHealth.STATE_CONNECTED});
- return devices;
- }
-
- List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) {
- List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states);
- return devices;
- }
-
- List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) {
- List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>();
-
- for (BluetoothDevice device: mHealthDevices.keySet()) {
- int healthDeviceState = getHealthDeviceConnectionState(device);
- for (int state : states) {
- if (state == healthDeviceState) {
- healthDevices.add(device);
- break;
- }
- }
- }
- return healthDevices;
- }
-
- /**
- * This function sends the intent for the updates on the connection status to the remote device.
- * Note that multiple channels can be connected to the remote device by multiple applications.
- * This sends an intent for the update to the device connection status and not the channel
- * connection status. Only the following state transitions are possible:
- *
- * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTING}
- * {@link BluetoothHealth#STATE_CONNECTING} to {@link BluetoothHealth#STATE_CONNECTED}
- * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTING}
- * {@link BluetoothHealth#STATE_DISCONNECTING} to {@link BluetoothHealth#STATE_DISCONNECTED}
- * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTED}
- * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTED}
- * {@link BluetoothHealth#STATE_CONNECTING} to {{@link BluetoothHealth#STATE_DISCONNECTED}
- *
- * @param device
- * @param prevChannelState
- * @param newChannelState
- * @hide
- */
- private void broadcastHealthDeviceStateChange(BluetoothDevice device, int prevChannelState,
- int newChannelState) {
- if (mHealthDevices.get(device) == null) {
- mHealthDevices.put(device, BluetoothHealth.STATE_DISCONNECTED);
- }
-
- int currDeviceState = mHealthDevices.get(device);
- int newDeviceState = convertState(newChannelState);
-
- if (currDeviceState != newDeviceState) {
- List<HealthChannel> chan;
- switch (currDeviceState) {
- case BluetoothHealth.STATE_DISCONNECTED:
- updateAndSendIntent(device, currDeviceState, newDeviceState);
- break;
- case BluetoothHealth.STATE_CONNECTING:
- // Channel got connected.
- if (newDeviceState == BluetoothHealth.STATE_CONNECTED) {
- updateAndSendIntent(device, currDeviceState, newDeviceState);
- } else {
- // Channel got disconnected
- chan = findChannelByStates(device, new int [] {
- BluetoothHealth.STATE_CHANNEL_CONNECTING,
- BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
- if (chan.isEmpty()) {
- updateAndSendIntent(device, currDeviceState, newDeviceState);
- }
- }
- break;
- case BluetoothHealth.STATE_CONNECTED:
- // Channel got disconnected or is in disconnecting state.
- chan = findChannelByStates(device, new int [] {
- BluetoothHealth.STATE_CHANNEL_CONNECTING,
- BluetoothHealth.STATE_CHANNEL_CONNECTED});
- if (chan.isEmpty()) {
- updateAndSendIntent(device, currDeviceState, newDeviceState);
- }
- break;
- case BluetoothHealth.STATE_DISCONNECTING:
- // Channel got disconnected.
- chan = findChannelByStates(device, new int [] {
- BluetoothHealth.STATE_CHANNEL_CONNECTING,
- BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
- if (chan.isEmpty()) {
- updateAndSendIntent(device, currDeviceState, newDeviceState);
- }
- break;
- }
- }
- }
-
- private void updateAndSendIntent(BluetoothDevice device, int prevDeviceState,
- int newDeviceState) {
- mHealthDevices.put(device, newDeviceState);
- mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.HEALTH,
- newDeviceState, prevDeviceState);
- }
-
- /**
- * This function converts the channel connection state to device connection state.
- *
- * @param state
- * @return
- */
- private int convertState(int state) {
- switch (state) {
- case BluetoothHealth.STATE_CHANNEL_CONNECTED:
- return BluetoothHealth.STATE_CONNECTED;
- case BluetoothHealth.STATE_CHANNEL_CONNECTING:
- return BluetoothHealth.STATE_CONNECTING;
- case BluetoothHealth.STATE_CHANNEL_DISCONNECTING:
- return BluetoothHealth.STATE_DISCONNECTING;
- case BluetoothHealth.STATE_CHANNEL_DISCONNECTED:
- return BluetoothHealth.STATE_DISCONNECTED;
- }
- errorLog("Mismatch in Channel and Health Device State");
- return -1;
- }
-
- private static void debugLog(String msg) {
- if (DBG) Log.d(TAG, msg);
- }
-
- private static void errorLog(String msg) {
- Log.e(TAG, msg);
- }
-}
diff --git a/core/java/android/server/BluetoothInputProfileHandler.java b/core/java/android/server/BluetoothInputProfileHandler.java
deleted file mode 100644
index 31764b0..0000000
--- a/core/java/android/server/BluetoothInputProfileHandler.java
+++ /dev/null
@@ -1,227 +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.server;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothDeviceProfileState;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothProfileState;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Message;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * This handles all the operations on the HID profile.
- * All functions are called by BluetoothService, as Bluetooth Service
- * is the Service handler for the HID profile.
- */
-final class BluetoothInputProfileHandler {
- private static final String TAG = "BluetoothInputProfileHandler";
- private static final boolean DBG = true;
-
- public static BluetoothInputProfileHandler sInstance;
- private Context mContext;
- private BluetoothService mBluetoothService;
- private final HashMap<BluetoothDevice, Integer> mInputDevices;
- private final BluetoothProfileState mHidProfileState;
-
- private BluetoothInputProfileHandler(Context context, BluetoothService service) {
- mContext = context;
- mBluetoothService = service;
- mInputDevices = new HashMap<BluetoothDevice, Integer>();
- mHidProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HID);
- mHidProfileState.start();
- }
-
- static synchronized BluetoothInputProfileHandler getInstance(Context context,
- BluetoothService service) {
- if (sInstance == null) sInstance = new BluetoothInputProfileHandler(context, service);
- return sInstance;
- }
-
- boolean connectInputDevice(BluetoothDevice device,
- BluetoothDeviceProfileState state) {
- String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
- if (objectPath == null ||
- getInputDeviceConnectionState(device) != BluetoothInputDevice.STATE_DISCONNECTED ||
- getInputDevicePriority(device) == BluetoothInputDevice.PRIORITY_OFF) {
- return false;
- }
- if (state != null) {
- Message msg = new Message();
- msg.arg1 = BluetoothDeviceProfileState.CONNECT_HID_OUTGOING;
- msg.obj = state;
- mHidProfileState.sendMessage(msg);
- return true;
- }
- return false;
- }
-
- boolean connectInputDeviceInternal(BluetoothDevice device) {
- String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
- handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTING);
- if (!mBluetoothService.connectInputDeviceNative(objectPath)) {
- handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTED);
- return false;
- }
- return true;
- }
-
- boolean disconnectInputDevice(BluetoothDevice device,
- BluetoothDeviceProfileState state) {
- String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
- if (objectPath == null ||
- getInputDeviceConnectionState(device) == BluetoothInputDevice.STATE_DISCONNECTED) {
- return false;
- }
- if (state != null) {
- Message msg = new Message();
- msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HID_OUTGOING;
- msg.obj = state;
- mHidProfileState.sendMessage(msg);
- return true;
- }
- return false;
- }
-
- boolean disconnectInputDeviceInternal(BluetoothDevice device) {
- String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
- handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_DISCONNECTING);
- if (!mBluetoothService.disconnectInputDeviceNative(objectPath)) {
- handleInputDeviceStateChange(device, BluetoothInputDevice.STATE_CONNECTED);
- return false;
- }
- return true;
- }
-
- int getInputDeviceConnectionState(BluetoothDevice device) {
- if (mInputDevices.get(device) == null) {
- return BluetoothInputDevice.STATE_DISCONNECTED;
- }
- return mInputDevices.get(device);
- }
-
- List<BluetoothDevice> getConnectedInputDevices() {
- List<BluetoothDevice> devices = lookupInputDevicesMatchingStates(
- new int[] {BluetoothInputDevice.STATE_CONNECTED});
- return devices;
- }
-
- List<BluetoothDevice> getInputDevicesMatchingConnectionStates(int[] states) {
- List<BluetoothDevice> devices = lookupInputDevicesMatchingStates(states);
- return devices;
- }
-
- int getInputDevicePriority(BluetoothDevice device) {
- return Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.getBluetoothInputDevicePriorityKey(device.getAddress()),
- BluetoothInputDevice.PRIORITY_UNDEFINED);
- }
-
- boolean setInputDevicePriority(BluetoothDevice device, int priority) {
- if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
- return false;
- }
- return Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.getBluetoothInputDevicePriorityKey(device.getAddress()),
- priority);
- }
-
- List<BluetoothDevice> lookupInputDevicesMatchingStates(int[] states) {
- List<BluetoothDevice> inputDevices = new ArrayList<BluetoothDevice>();
-
- for (BluetoothDevice device: mInputDevices.keySet()) {
- int inputDeviceState = getInputDeviceConnectionState(device);
- for (int state : states) {
- if (state == inputDeviceState) {
- inputDevices.add(device);
- break;
- }
- }
- }
- return inputDevices;
- }
-
- private void handleInputDeviceStateChange(BluetoothDevice device, int state) {
- int prevState;
- if (mInputDevices.get(device) == null) {
- prevState = BluetoothInputDevice.STATE_DISCONNECTED;
- } else {
- prevState = mInputDevices.get(device);
- }
- if (prevState == state) return;
-
- mInputDevices.put(device, state);
-
- if (getInputDevicePriority(device) >
- BluetoothInputDevice.PRIORITY_OFF &&
- state == BluetoothInputDevice.STATE_CONNECTING ||
- state == BluetoothInputDevice.STATE_CONNECTED) {
- // We have connected or attempting to connect.
- // Bump priority
- setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_AUTO_CONNECT);
- }
-
- Intent intent = new Intent(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothInputDevice.EXTRA_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothInputDevice.EXTRA_STATE, state);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM);
-
- debugLog("InputDevice state : device: " + device + " State:" + prevState + "->" + state);
- mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.INPUT_DEVICE, state,
- prevState);
- }
-
- void handleInputDevicePropertyChange(String address, boolean connected) {
- int state = connected ? BluetoothInputDevice.STATE_CONNECTED :
- BluetoothInputDevice.STATE_DISCONNECTED;
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- BluetoothDevice device = adapter.getRemoteDevice(address);
- handleInputDeviceStateChange(device, state);
- }
-
- void setInitialInputDevicePriority(BluetoothDevice device, int state) {
- switch (state) {
- case BluetoothDevice.BOND_BONDED:
- if (getInputDevicePriority(device) == BluetoothInputDevice.PRIORITY_UNDEFINED) {
- setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_ON);
- }
- break;
- case BluetoothDevice.BOND_NONE:
- setInputDevicePriority(device, BluetoothInputDevice.PRIORITY_UNDEFINED);
- break;
- }
- }
-
- private static void debugLog(String msg) {
- if (DBG) Log.d(TAG, msg);
- }
-
- private static void errorLog(String msg) {
- Log.e(TAG, msg);
- }
-}
diff --git a/core/java/android/server/BluetoothPanProfileHandler.java b/core/java/android/server/BluetoothPanProfileHandler.java
deleted file mode 100644
index 41bb87f..0000000
--- a/core/java/android/server/BluetoothPanProfileHandler.java
+++ /dev/null
@@ -1,409 +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.server;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothTetheringDataTracker;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources.NotFoundException;
-import android.net.ConnectivityManager;
-import android.net.InterfaceConfiguration;
-import android.net.LinkAddress;
-import android.net.NetworkUtils;
-import android.os.IBinder;
-import android.os.INetworkManagementService;
-import android.os.ServiceManager;
-import android.util.Log;
-
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * This handles the PAN profile. All calls into this are made
- * from Bluetooth Service.
- */
-final class BluetoothPanProfileHandler {
- private static final String TAG = "BluetoothPanProfileHandler";
- private static final boolean DBG = true;
-
- private ArrayList<String> mBluetoothIfaceAddresses;
- private int mMaxPanDevices;
-
- private static final String BLUETOOTH_IFACE_ADDR_START= "192.168.44.1";
- private static final int BLUETOOTH_MAX_PAN_CONNECTIONS = 5;
- private static final int BLUETOOTH_PREFIX_LENGTH = 24;
- public static BluetoothPanProfileHandler sInstance;
- private final HashMap<BluetoothDevice, BluetoothPanDevice> mPanDevices;
- private boolean mTetheringOn;
- private Context mContext;
- private BluetoothService mBluetoothService;
-
- static final String NAP_ROLE = "nap";
- static final String NAP_BRIDGE = "pan1";
-
- private BluetoothPanProfileHandler(Context context, BluetoothService service) {
- mContext = context;
- mPanDevices = new HashMap<BluetoothDevice, BluetoothPanDevice>();
- mBluetoothService = service;
- mTetheringOn = false;
- mBluetoothIfaceAddresses = new ArrayList<String>();
- try {
- mMaxPanDevices = context.getResources().getInteger(
- com.android.internal.R.integer.config_max_pan_devices);
- } catch (NotFoundException e) {
- mMaxPanDevices = BLUETOOTH_MAX_PAN_CONNECTIONS;
- }
- }
-
- static BluetoothPanProfileHandler getInstance(Context context,
- BluetoothService service) {
- if (sInstance == null) sInstance = new BluetoothPanProfileHandler(context, service);
- return sInstance;
- }
-
- boolean isTetheringOn() {
- return mTetheringOn;
- }
-
- boolean allowIncomingTethering() {
- if (isTetheringOn() && getConnectedPanDevices().size() < mMaxPanDevices)
- return true;
- return false;
- }
-
- private BroadcastReceiver mTetheringReceiver = null;
-
- void setBluetoothTethering(boolean value) {
- if (!value) {
- disconnectPanServerDevices();
- }
-
- if (mBluetoothService.getBluetoothState() != BluetoothAdapter.STATE_ON && value) {
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- mTetheringReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
- == BluetoothAdapter.STATE_ON) {
- mTetheringOn = true;
- mContext.unregisterReceiver(mTetheringReceiver);
- }
- }
- };
- mContext.registerReceiver(mTetheringReceiver, filter);
- } else {
- mTetheringOn = value;
- }
- }
-
- int getPanDeviceConnectionState(BluetoothDevice device) {
- BluetoothPanDevice panDevice = mPanDevices.get(device);
- if (panDevice == null) {
- return BluetoothPan.STATE_DISCONNECTED;
- }
- return panDevice.mState;
- }
-
- boolean connectPanDevice(BluetoothDevice device) {
- String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
- if (DBG) Log.d(TAG, "connect PAN(" + objectPath + ")");
- if (getPanDeviceConnectionState(device) != BluetoothPan.STATE_DISCONNECTED) {
- errorLog(device + " already connected to PAN");
- }
-
- int connectedCount = 0;
- for (BluetoothDevice panDevice: mPanDevices.keySet()) {
- if (getPanDeviceConnectionState(panDevice) == BluetoothPan.STATE_CONNECTED) {
- connectedCount ++;
- }
- }
- if (connectedCount > 8) {
- debugLog(device + " could not connect to PAN because 8 other devices are"
- + "already connected");
- return false;
- }
-
- // Send interface as null as it is not known
- handlePanDeviceStateChange(device, null, BluetoothPan.STATE_CONNECTING,
- BluetoothPan.LOCAL_PANU_ROLE);
- if (mBluetoothService.connectPanDeviceNative(objectPath, "nap")) {
- debugLog("connecting to PAN");
- return true;
- } else {
- handlePanDeviceStateChange(device, null, BluetoothPan.STATE_DISCONNECTED,
- BluetoothPan.LOCAL_PANU_ROLE);
- errorLog("could not connect to PAN");
- return false;
- }
- }
-
- private boolean disconnectPanServerDevices() {
- debugLog("disconnect all PAN devices");
-
- for (BluetoothDevice device: mPanDevices.keySet()) {
- BluetoothPanDevice panDevice = mPanDevices.get(device);
- int state = panDevice.mState;
- if (state == BluetoothPan.STATE_CONNECTED &&
- panDevice.mLocalRole == BluetoothPan.LOCAL_NAP_ROLE) {
- String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
-
- handlePanDeviceStateChange(device, panDevice.mIface,
- BluetoothPan.STATE_DISCONNECTING, panDevice.mLocalRole);
-
- if (!mBluetoothService.disconnectPanServerDeviceNative(objectPath,
- device.getAddress(),
- panDevice.mIface)) {
- errorLog("could not disconnect Pan Server Device "+device.getAddress());
-
- // Restore prev state
- handlePanDeviceStateChange(device, panDevice.mIface, state,
- panDevice.mLocalRole);
-
- return false;
- }
- }
- }
- return true;
- }
-
- List<BluetoothDevice> getConnectedPanDevices() {
- List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
-
- for (BluetoothDevice device: mPanDevices.keySet()) {
- if (getPanDeviceConnectionState(device) == BluetoothPan.STATE_CONNECTED) {
- devices.add(device);
- }
- }
- return devices;
- }
-
- List<BluetoothDevice> getPanDevicesMatchingConnectionStates(int[] states) {
- List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
-
- for (BluetoothDevice device: mPanDevices.keySet()) {
- int panDeviceState = getPanDeviceConnectionState(device);
- for (int state : states) {
- if (state == panDeviceState) {
- devices.add(device);
- break;
- }
- }
- }
- return devices;
- }
-
- boolean disconnectPanDevice(BluetoothDevice device) {
- String objectPath = mBluetoothService.getObjectPathFromAddress(device.getAddress());
- debugLog("disconnect PAN(" + objectPath + ")");
-
- int state = getPanDeviceConnectionState(device);
- if (state != BluetoothPan.STATE_CONNECTED) {
- debugLog(device + " already disconnected from PAN");
- return false;
- }
-
- BluetoothPanDevice panDevice = mPanDevices.get(device);
-
- if (panDevice == null) {
- errorLog("No record for this Pan device:" + device);
- return false;
- }
-
- handlePanDeviceStateChange(device, panDevice.mIface, BluetoothPan.STATE_DISCONNECTING,
- panDevice.mLocalRole);
- if (panDevice.mLocalRole == BluetoothPan.LOCAL_NAP_ROLE) {
- if (!mBluetoothService.disconnectPanServerDeviceNative(objectPath, device.getAddress(),
- panDevice.mIface)) {
- // Restore prev state, this shouldn't happen
- handlePanDeviceStateChange(device, panDevice.mIface, state, panDevice.mLocalRole);
- return false;
- }
- } else {
- if (!mBluetoothService.disconnectPanDeviceNative(objectPath)) {
- // Restore prev state, this shouldn't happen
- handlePanDeviceStateChange(device, panDevice.mIface, state, panDevice.mLocalRole);
- return false;
- }
- }
- return true;
- }
-
- void handlePanDeviceStateChange(BluetoothDevice device,
- String iface, int state, int role) {
- int prevState;
- String ifaceAddr = null;
- BluetoothPanDevice panDevice = mPanDevices.get(device);
-
- if (panDevice == null) {
- prevState = BluetoothPan.STATE_DISCONNECTED;
- } else {
- prevState = panDevice.mState;
- ifaceAddr = panDevice.mIfaceAddr;
- }
- if (prevState == state) return;
-
- if (role == BluetoothPan.LOCAL_NAP_ROLE) {
- if (state == BluetoothPan.STATE_CONNECTED) {
- ifaceAddr = enableTethering(iface);
- if (ifaceAddr == null) Log.e(TAG, "Error seting up tether interface");
- } else if (state == BluetoothPan.STATE_DISCONNECTED) {
- if (ifaceAddr != null) {
- mBluetoothIfaceAddresses.remove(ifaceAddr);
- ifaceAddr = null;
- }
- }
- } else {
- // PANU Role = reverse Tether
- if (state == BluetoothPan.STATE_CONNECTED) {
- BluetoothTetheringDataTracker.getInstance().startReverseTether(iface, device);
- } else if (state == BluetoothPan.STATE_DISCONNECTED &&
- (prevState == BluetoothPan.STATE_CONNECTED ||
- prevState == BluetoothPan.STATE_DISCONNECTING)) {
- BluetoothTetheringDataTracker.getInstance().stopReverseTether(panDevice.mIface);
- }
- }
-
- if (panDevice == null) {
- panDevice = new BluetoothPanDevice(state, ifaceAddr, iface, role);
- mPanDevices.put(device, panDevice);
- } else {
- panDevice.mState = state;
- panDevice.mIfaceAddr = ifaceAddr;
- panDevice.mLocalRole = role;
- panDevice.mIface = iface;
- }
-
- Intent intent = new Intent(BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothPan.EXTRA_PREVIOUS_STATE, prevState);
- intent.putExtra(BluetoothPan.EXTRA_STATE, state);
- intent.putExtra(BluetoothPan.EXTRA_LOCAL_ROLE, role);
- mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM);
-
- debugLog("Pan Device state : device: " + device + " State:" + prevState + "->" + state);
- mBluetoothService.sendConnectionStateChange(device, BluetoothProfile.PAN, state,
- prevState);
- }
-
- private class BluetoothPanDevice {
- private int mState;
- private String mIfaceAddr;
- private String mIface;
- private int mLocalRole; // Which local role is this PAN device bound to
-
- BluetoothPanDevice(int state, String ifaceAddr, String iface, int localRole) {
- mState = state;
- mIfaceAddr = ifaceAddr;
- mIface = iface;
- mLocalRole = localRole;
- }
- }
-
- private String createNewTetheringAddressLocked() {
- if (getConnectedPanDevices().size() == mMaxPanDevices) {
- debugLog ("Max PAN device connections reached");
- return null;
- }
- String address = BLUETOOTH_IFACE_ADDR_START;
- while (true) {
- if (mBluetoothIfaceAddresses.contains(address)) {
- String[] addr = address.split("\\.");
- Integer newIp = Integer.parseInt(addr[2]) + 1;
- address = address.replace(addr[2], newIp.toString());
- } else {
- break;
- }
- }
- mBluetoothIfaceAddresses.add(address);
- return address;
- }
-
- // configured when we start tethering
- private String enableTethering(String iface) {
- debugLog("updateTetherState:" + iface);
-
- IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
- INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
- ConnectivityManager cm =
- (ConnectivityManager)mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- String[] bluetoothRegexs = cm.getTetherableBluetoothRegexs();
-
- // bring toggle the interfaces
- String[] currentIfaces = new String[0];
- try {
- currentIfaces = service.listInterfaces();
- } catch (Exception e) {
- Log.e(TAG, "Error listing Interfaces :" + e);
- return null;
- }
-
- boolean found = false;
- for (String currIface: currentIfaces) {
- if (currIface.equals(iface)) {
- found = true;
- break;
- }
- }
-
- if (!found) return null;
-
- String address = createNewTetheringAddressLocked();
- if (address == null) return null;
-
- InterfaceConfiguration ifcg = null;
- try {
- ifcg = service.getInterfaceConfig(iface);
- if (ifcg != null) {
- final LinkAddress linkAddr = ifcg.getLinkAddress();
- InetAddress addr = null;
- if (linkAddr == null || (addr = linkAddr.getAddress()) == null ||
- addr.equals(NetworkUtils.numericToInetAddress("0.0.0.0")) ||
- addr.equals(NetworkUtils.numericToInetAddress("::0"))) {
- addr = NetworkUtils.numericToInetAddress(address);
- }
- ifcg.setInterfaceUp();
- ifcg.clearFlag("running");
- ifcg.setLinkAddress(new LinkAddress(addr, BLUETOOTH_PREFIX_LENGTH));
- service.setInterfaceConfig(iface, ifcg);
- if (cm.tether(iface) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
- Log.e(TAG, "Error tethering "+iface);
- }
- }
- } catch (Exception e) {
- Log.e(TAG, "Error configuring interface " + iface + ", :" + e);
- return null;
- }
- return address;
- }
-
- private static void debugLog(String msg) {
- if (DBG) Log.d(TAG, msg);
- }
-
- private static void errorLog(String msg) {
- Log.e(TAG, msg);
- }
-}
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
deleted file mode 100755
index 6296b11..0000000
--- a/core/java/android/server/BluetoothService.java
+++ /dev/null
@@ -1,2924 +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.
- */
-
-/**
- * TODO: Move this to
- * java/services/com/android/server/BluetoothService.java
- * and make the contructor package private again.
- *
- * @hide
- */
-
-package android.server;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothDeviceProfileState;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHealthAppConfiguration;
-import android.bluetooth.BluetoothInputDevice;
-import android.bluetooth.BluetoothPan;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothProfileState;
-import android.bluetooth.BluetoothSocket;
-import android.bluetooth.BluetoothUuid;
-import android.bluetooth.IBluetooth;
-import android.bluetooth.IBluetoothCallback;
-import android.bluetooth.IBluetoothHealthCallback;
-import android.bluetooth.IBluetoothStateChangeCallback;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.ParcelUuid;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.provider.Settings;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.app.IBatteryStats;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.DataInputStream;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
-import java.io.RandomAccessFile;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-public class BluetoothService extends IBluetooth.Stub {
- private static final String TAG = "BluetoothService";
- private static final boolean DBG = true;
-
- private int mNativeData;
- private BluetoothEventLoop mEventLoop;
- private BluetoothHeadset mHeadsetProxy;
- private BluetoothInputDevice mInputDevice;
- private BluetoothPan mPan;
- private boolean mIsAirplaneSensitive;
- private boolean mIsAirplaneToggleable;
- private BluetoothAdapterStateMachine mBluetoothState;
- private int[] mAdapterSdpHandles;
- private ParcelUuid[] mAdapterUuids;
-
- private BluetoothAdapter mAdapter; // constant after init()
- private final BluetoothBondState mBondState; // local cache of bondings
- private final IBatteryStats mBatteryStats;
- private final Context mContext;
- private Map<Integer, IBluetoothStateChangeCallback> mStateChangeTracker =
- Collections.synchronizedMap(new HashMap<Integer, IBluetoothStateChangeCallback>());
-
- private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
- static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
-
- private static final String DOCK_ADDRESS_PATH = "/sys/class/switch/dock/bt_addr";
- private static final String DOCK_PIN_PATH = "/sys/class/switch/dock/bt_pin";
-
- private static final String SHARED_PREFERENCE_DOCK_ADDRESS = "dock_bluetooth_address";
- private static final String SHARED_PREFERENCES_NAME = "bluetooth_service_settings";
-
- private static final int MESSAGE_UUID_INTENT = 1;
- private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 2;
- private static final int MESSAGE_REMOVE_SERVICE_RECORD = 3;
-
- private static final int RFCOMM_RECORD_REAPER = 10;
- private static final int STATE_CHANGE_REAPER = 11;
-
- // The time (in millisecs) to delay the pairing attempt after the first
- // auto pairing attempt fails. We use an exponential delay with
- // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
- // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
- private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
- private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
-
- // The timeout used to sent the UUIDs Intent
- // This timeout should be greater than the page timeout
- private static final int UUID_INTENT_DELAY = 6000;
-
- /** Always retrieve RFCOMM channel for these SDP UUIDs */
- private static final ParcelUuid[] RFCOMM_UUIDS = {
- BluetoothUuid.Handsfree,
- BluetoothUuid.HSP,
- BluetoothUuid.ObexObjectPush };
-
- private final BluetoothAdapterProperties mAdapterProperties;
- private final BluetoothDeviceProperties mDeviceProperties;
-
- private final HashMap<String, Map<ParcelUuid, Integer>> mDeviceServiceChannelCache;
- private final ArrayList<String> mUuidIntentTracker;
- private final HashMap<RemoteService, IBluetoothCallback> mUuidCallbackTracker;
-
- private static class ServiceRecordClient {
- int pid;
- IBinder binder;
- IBinder.DeathRecipient death;
- }
- private final HashMap<Integer, ServiceRecordClient> mServiceRecordToPid;
-
- private final HashMap<String, BluetoothDeviceProfileState> mDeviceProfileState;
- private final BluetoothProfileState mA2dpProfileState;
- private final BluetoothProfileState mHfpProfileState;
-
- private BluetoothA2dpService mA2dpService;
- private final HashMap<String, Pair<byte[], byte[]>> mDeviceOobData;
-
- private int mProfilesConnected = 0, mProfilesConnecting = 0, mProfilesDisconnecting = 0;
-
- private static String mDockAddress;
- private String mDockPin;
-
- private boolean mAllowConnect = true;
-
- private int mAdapterConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
- private BluetoothPanProfileHandler mBluetoothPanProfileHandler;
- private BluetoothInputProfileHandler mBluetoothInputProfileHandler;
- private BluetoothHealthProfileHandler mBluetoothHealthProfileHandler;
- private static final String INCOMING_CONNECTION_FILE =
- "/data/misc/bluetooth/incoming_connection.conf";
- private HashMap<String, Pair<Integer, String>> mIncomingConnections;
- private HashMap<Integer, Pair<Integer, Integer>> mProfileConnectionState;
-
- private static class RemoteService {
- public String address;
- public ParcelUuid uuid;
- public RemoteService(String address, ParcelUuid uuid) {
- this.address = address;
- this.uuid = uuid;
- }
- @Override
- public boolean equals(Object o) {
- if (o instanceof RemoteService) {
- RemoteService service = (RemoteService)o;
- return address.equals(service.address) && uuid.equals(service.uuid);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- int hash = 1;
- hash = hash * 31 + (address == null ? 0 : address.hashCode());
- hash = hash * 31 + (uuid == null ? 0 : uuid.hashCode());
- return hash;
- }
- }
-
- static {
- classInitNative();
- }
-
- public BluetoothService(Context context) {
- mContext = context;
-
- // Need to do this in place of:
- // mBatteryStats = BatteryStatsService.getService();
- // Since we can not import BatteryStatsService from here. This class really needs to be
- // moved to java/services/com/android/server/
- mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
-
- initializeNativeDataNative();
-
- if (isEnabledNative() == 1) {
- Log.w(TAG, "Bluetooth daemons already running - runtime restart? ");
- disableNative();
- }
-
- mBondState = new BluetoothBondState(context, this);
- mAdapterProperties = new BluetoothAdapterProperties(context, this);
- mDeviceProperties = new BluetoothDeviceProperties(this);
-
- mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
- mDeviceOobData = new HashMap<String, Pair<byte[], byte[]>>();
- mUuidIntentTracker = new ArrayList<String>();
- mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
- mServiceRecordToPid = new HashMap<Integer, ServiceRecordClient>();
- mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>();
- mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP);
- mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
-
- mHfpProfileState.start();
- mA2dpProfileState.start();
-
- IntentFilter filter = new IntentFilter();
- registerForAirplaneMode(filter);
-
- filter.addAction(Intent.ACTION_DOCK_EVENT);
- mContext.registerReceiver(mReceiver, filter);
- mBluetoothInputProfileHandler = BluetoothInputProfileHandler.getInstance(mContext, this);
- mBluetoothPanProfileHandler = BluetoothPanProfileHandler.getInstance(mContext, this);
- mBluetoothHealthProfileHandler = BluetoothHealthProfileHandler.getInstance(mContext, this);
- mIncomingConnections = new HashMap<String, Pair<Integer, String>>();
- mProfileConnectionState = new HashMap<Integer, Pair<Integer, Integer>>();
- }
-
- public static synchronized String readDockBluetoothAddress() {
- if (mDockAddress != null) return mDockAddress;
-
- BufferedInputStream file = null;
- String dockAddress;
- try {
- file = new BufferedInputStream(new FileInputStream(DOCK_ADDRESS_PATH));
- byte[] address = new byte[17];
- file.read(address);
- dockAddress = new String(address);
- dockAddress = dockAddress.toUpperCase();
- if (BluetoothAdapter.checkBluetoothAddress(dockAddress)) {
- mDockAddress = dockAddress;
- return mDockAddress;
- } else {
- Log.e(TAG, "CheckBluetoothAddress failed for car dock address: "
- + dockAddress);
- }
- } catch (FileNotFoundException e) {
- Log.e(TAG, "FileNotFoundException while trying to read dock address");
- } catch (IOException e) {
- Log.e(TAG, "IOException while trying to read dock address");
- } finally {
- if (file != null) {
- try {
- file.close();
- } catch (IOException e) {
- // Ignore
- }
- }
- }
- mDockAddress = null;
- return null;
- }
-
- private synchronized boolean writeDockPin() {
- BufferedWriter out = null;
- try {
- out = new BufferedWriter(new FileWriter(DOCK_PIN_PATH));
-
- // Generate a random 4 digit pin between 0000 and 9999
- // This is not truly random but good enough for our purposes.
- int pin = (int) Math.floor(Math.random() * 10000);
-
- mDockPin = String.format("%04d", pin);
- out.write(mDockPin);
- return true;
- } catch (FileNotFoundException e) {
- Log.e(TAG, "FileNotFoundException while trying to write dock pairing pin");
- } catch (IOException e) {
- Log.e(TAG, "IOException while while trying to write dock pairing pin");
- } finally {
- if (out != null) {
- try {
- out.close();
- } catch (IOException e) {
- // Ignore
- }
- }
- }
- mDockPin = null;
- return false;
- }
-
- /*package*/ synchronized String getDockPin() {
- return mDockPin;
- }
-
- public synchronized void initAfterRegistration() {
- mAdapter = BluetoothAdapter.getDefaultAdapter();
- mBluetoothState = new BluetoothAdapterStateMachine(mContext, this, mAdapter);
- mBluetoothState.start();
- if (mContext.getResources().getBoolean
- (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
- mBluetoothState.sendMessage(BluetoothAdapterStateMachine.TURN_HOT);
- }
- mEventLoop = mBluetoothState.getBluetoothEventLoop();
- }
-
- public synchronized void initAfterA2dpRegistration() {
- mEventLoop.getProfileProxy();
- }
-
- @Override
- protected void finalize() throws Throwable {
- mContext.unregisterReceiver(mReceiver);
- try {
- cleanupNativeDataNative();
- } finally {
- super.finalize();
- }
- }
-
- public boolean isEnabled() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return isEnabledInternal();
- }
-
- private boolean isEnabledInternal() {
- return (getBluetoothStateInternal() == BluetoothAdapter.STATE_ON);
- }
-
- public int getBluetoothState() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return getBluetoothStateInternal();
- }
-
- int getBluetoothStateInternal() {
- return mBluetoothState.getBluetoothAdapterState();
- }
-
- /**
- * Bring down bluetooth and disable BT in settings. Returns true on success.
- */
- public boolean disable() {
- return disable(true);
- }
-
- /**
- * Bring down bluetooth. Returns true on success.
- *
- * @param saveSetting If true, persist the new setting
- */
- public synchronized boolean disable(boolean saveSetting) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
-
- int adapterState = getBluetoothStateInternal();
-
- switch (adapterState) {
- case BluetoothAdapter.STATE_OFF:
- return true;
- case BluetoothAdapter.STATE_ON:
- break;
- default:
- return false;
- }
-
- mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_OFF, saveSetting);
- return true;
- }
-
- synchronized void disconnectDevices() {
- // Disconnect devices handled by BluetoothService.
- for (BluetoothDevice device: getConnectedInputDevices()) {
- disconnectInputDevice(device);
- }
-
- for (BluetoothDevice device: getConnectedPanDevices()) {
- disconnectPanDevice(device);
- }
- }
-
- /**
- * The Bluetooth has been turned off, but hot. Do bonding, profile cleanup
- */
- synchronized void finishDisable() {
- // mark in progress bondings as cancelled
- for (String address : mBondState.listInState(BluetoothDevice.BOND_BONDING)) {
- mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
- BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
- }
-
- // Stop the profile state machine for bonded devices.
- for (String address : mBondState.listInState(BluetoothDevice.BOND_BONDED)) {
- removeProfileState(address);
- }
-
- // update mode
- Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
- intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, BluetoothAdapter.SCAN_MODE_NONE);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- }
-
- /**
- * Local clean up after broadcasting STATE_OFF intent
- */
- synchronized void cleanupAfterFinishDisable() {
- mAdapterProperties.clear();
-
- for (Integer srHandle : mServiceRecordToPid.keySet()) {
- removeServiceRecordNative(srHandle);
- }
- mServiceRecordToPid.clear();
-
- mProfilesConnected = 0;
- mProfilesConnecting = 0;
- mProfilesDisconnecting = 0;
- mAdapterConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
- mAdapterUuids = null;
- mAdapterSdpHandles = null;
-
- // Log bluetooth off to battery stats.
- long ident = Binder.clearCallingIdentity();
- try {
- mBatteryStats.noteBluetoothOff();
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * power off Bluetooth
- */
- synchronized void shutoffBluetooth() {
- if (mAdapterSdpHandles != null) removeReservedServiceRecordsNative(mAdapterSdpHandles);
- setBluetoothTetheringNative(false, BluetoothPanProfileHandler.NAP_ROLE,
- BluetoothPanProfileHandler.NAP_BRIDGE);
- tearDownNativeDataNative();
- }
-
- /**
- * Data clean up after Bluetooth shutoff
- */
- synchronized void cleanNativeAfterShutoffBluetooth() {
- // Ths method is called after shutdown of event loop in the Bluetooth shut down
- // procedure
-
- // the adapter property could be changed before event loop is stoped, clear it again
- mAdapterProperties.clear();
- disableNative();
- }
-
- /** Bring up BT and persist BT on in settings */
- public boolean enable() {
- return enable(true, true);
- }
-
- /**
- * Enable this Bluetooth device, asynchronously.
- * This turns on/off the underlying hardware.
- *
- * @param saveSetting If true, persist the new state of BT in settings
- * @param allowConnect If true, auto-connects device when BT is turned on
- * and allows incoming A2DP/HSP connections
- * @return True on success (so far)
- */
- public synchronized boolean enable(boolean saveSetting, boolean allowConnect) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
-
- // Airplane mode can prevent Bluetooth radio from being turned on.
- if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) {
- return false;
- }
- mAllowConnect = allowConnect;
- mBluetoothState.sendMessage(BluetoothAdapterStateMachine.USER_TURN_ON, saveSetting);
- return true;
- }
-
- /**
- * Enable this Bluetooth device, asynchronously, but does not
- * auto-connect devices. In this state the Bluetooth adapter
- * also does not allow incoming A2DP/HSP connections (that
- * must go through this service), but does allow communication
- * on RFCOMM sockets implemented outside of this service (ie BTOPP).
- * This method is used to temporarily enable Bluetooth
- * for data transfer, without changing
- *
- * This turns on/off the underlying hardware.
- *
- * @return True on success (so far)
- */
- public boolean enableNoAutoConnect() {
- return enable(false, false);
- }
-
- /**
- * Turn on Bluetooth Module, Load firmware, and do all the preparation
- * needed to get the Bluetooth Module ready but keep it not discoverable
- * and not connectable.
- */
- /* package */ synchronized boolean prepareBluetooth() {
- if (!setupNativeDataNative()) {
- return false;
- }
- switchConnectable(false);
-
- // Bluetooth stack needs a small delay here before adding
- // SDP records, otherwise dbus stalls for over 30 seconds 1 out of 50 runs
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {}
- updateSdpRecords();
- return true;
- }
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_UUID_INTENT:
- String address = (String)msg.obj;
- if (address != null) {
- sendUuidIntent(address);
- makeServiceChannelCallbacks(address);
- }
- break;
- case MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
- address = (String)msg.obj;
- if (address == null) return;
- int attempt = mBondState.getAttempt(address);
-
- // Try only if attemps are in progress and cap it 2 attempts
- // The 2 attempts cap is a fail safe if the stack returns
- // an incorrect error code for bonding failures and if the pin
- // is entered wrongly twice we should abort.
- if (attempt > 0 && attempt <= 2) {
- mBondState.attempt(address);
- createBond(address);
- return;
- }
- if (attempt > 0) mBondState.clearPinAttempts(address);
- break;
- case MESSAGE_REMOVE_SERVICE_RECORD:
- Pair<Integer, Integer> pair = (Pair<Integer, Integer>) msg.obj;
- checkAndRemoveRecord(pair.first, pair.second);
- break;
- }
- }
- };
-
- private synchronized void addReservedSdpRecords(final ArrayList<ParcelUuid> uuids) {
- //Register SDP records.
- int[] svcIdentifiers = new int[uuids.size()];
- for (int i = 0; i < uuids.size(); i++) {
- svcIdentifiers[i] = BluetoothUuid.getServiceIdentifierFromParcelUuid(uuids.get(i));
- }
- mAdapterSdpHandles = addReservedServiceRecordsNative(svcIdentifiers);
- }
-
- private synchronized void updateSdpRecords() {
- ArrayList<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
-
- Resources R = mContext.getResources();
-
- // Add the default records
- if (R.getBoolean(com.android.internal.R.bool.config_bluetooth_default_profiles)) {
- uuids.add(BluetoothUuid.HSP_AG);
- uuids.add(BluetoothUuid.ObexObjectPush);
- }
-
- if (R.getBoolean(com.android.internal.R.bool.config_voice_capable)) {
- uuids.add(BluetoothUuid.Handsfree_AG);
- uuids.add(BluetoothUuid.PBAP_PSE);
- }
-
- // Add SDP records for profiles maintained by Android userspace
- addReservedSdpRecords(uuids);
-
- // Bluetooth stack need some a small delay here before adding more
- // SDP records, otherwise dbus stalls for over 30 seconds 1 out of 50 runs
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {}
-
- if (R.getBoolean(com.android.internal.R.bool.config_bluetooth_default_profiles)) {
- // Enable profiles maintained by Bluez userspace.
- setBluetoothTetheringNative(true, BluetoothPanProfileHandler.NAP_ROLE,
- BluetoothPanProfileHandler.NAP_BRIDGE);
-
- // Add SDP records for profiles maintained by Bluez userspace
- uuids.add(BluetoothUuid.AudioSource);
- uuids.add(BluetoothUuid.AvrcpTarget);
- uuids.add(BluetoothUuid.NAP);
- }
-
- // Cannot cast uuids.toArray directly since ParcelUuid is parcelable
- mAdapterUuids = new ParcelUuid[uuids.size()];
- for (int i = 0; i < uuids.size(); i++) {
- mAdapterUuids[i] = uuids.get(i);
- }
- }
-
- /**
- * This function is called from Bluetooth Event Loop when onPropertyChanged
- * for adapter comes in with UUID property.
- * @param uuidsThe uuids of adapter as reported by Bluez.
- */
- /*package*/ synchronized void updateBluetoothState(String uuids) {
- ParcelUuid[] adapterUuids = convertStringToParcelUuid(uuids);
-
- if (mAdapterUuids != null &&
- BluetoothUuid.containsAllUuids(adapterUuids, mAdapterUuids)) {
- mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SERVICE_RECORD_LOADED);
- }
- }
-
- /**
- * This method is called immediately before Bluetooth module is turned on after
- * the adapter became pariable.
- * It inits bond state and profile state before STATE_ON intent is broadcasted.
- */
- /*package*/ void initBluetoothAfterTurningOn() {
- String discoverable = getProperty("Discoverable", false);
- String timeout = getProperty("DiscoverableTimeout", false);
- if (timeout == null) {
- Log.w(TAG, "Null DiscoverableTimeout property");
- // assign a number, anything not 0
- timeout = "1";
- }
- if (discoverable.equals("true") && Integer.valueOf(timeout) != 0) {
- setAdapterPropertyBooleanNative("Discoverable", 0);
- }
- mBondState.initBondState();
- initProfileState();
- getProfileProxy();
- }
-
- /**
- * This method is called immediately after Bluetooth module is turned on.
- * It starts auto-connection and places bluetooth on sign onto the battery
- * stats
- */
- /*package*/ void runBluetooth() {
- autoConnect();
-
- // Log bluetooth on to battery stats.
- long ident = Binder.clearCallingIdentity();
- try {
- mBatteryStats.noteBluetoothOn();
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /*package*/ synchronized boolean attemptAutoPair(String address) {
- if (!mBondState.hasAutoPairingFailed(address) &&
- !mBondState.isAutoPairingBlacklisted(address)) {
- mBondState.attempt(address);
- setPin(address, BluetoothDevice.convertPinToBytes("0000"));
- return true;
- }
- return false;
- }
-
- /*package*/ synchronized boolean isFixedPinZerosAutoPairKeyboard(String address) {
- // Check for keyboards which have fixed PIN 0000 as the pairing pin
- return mBondState.isFixedPinZerosAutoPairKeyboard(address);
- }
-
- /*package*/ synchronized void onCreatePairedDeviceResult(String address, int result) {
- if (result == BluetoothDevice.BOND_SUCCESS) {
- setBondState(address, BluetoothDevice.BOND_BONDED);
- if (mBondState.isAutoPairingAttemptsInProgress(address)) {
- mBondState.clearPinAttempts(address);
- }
- } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
- mBondState.getAttempt(address) == 1) {
- mBondState.addAutoPairingFailure(address);
- pairingAttempt(address, result);
- } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
- mBondState.isAutoPairingAttemptsInProgress(address)) {
- pairingAttempt(address, result);
- } else {
- setBondState(address, BluetoothDevice.BOND_NONE, result);
- if (mBondState.isAutoPairingAttemptsInProgress(address)) {
- mBondState.clearPinAttempts(address);
- }
- }
- }
-
- /*package*/ synchronized String getPendingOutgoingBonding() {
- return mBondState.getPendingOutgoingBonding();
- }
-
- private void pairingAttempt(String address, int result) {
- // This happens when our initial guess of "0000" as the pass key
- // fails. Try to create the bond again and display the pin dialog
- // to the user. Use back-off while posting the delayed
- // message. The initial value is
- // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
- // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
- // reached, display an error to the user.
- int attempt = mBondState.getAttempt(address);
- if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
- MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
- mBondState.clearPinAttempts(address);
- setBondState(address, BluetoothDevice.BOND_NONE, result);
- return;
- }
-
- Message message = mHandler.obtainMessage(MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
- message.obj = address;
- boolean postResult = mHandler.sendMessageDelayed(message,
- attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
- if (!postResult) {
- mBondState.clearPinAttempts(address);
- setBondState(address,
- BluetoothDevice.BOND_NONE, result);
- return;
- }
- }
-
- /*package*/ BluetoothDevice getRemoteDevice(String address) {
- return mAdapter.getRemoteDevice(address);
- }
-
- private static String toBondStateString(int bondState) {
- switch (bondState) {
- case BluetoothDevice.BOND_NONE:
- return "not bonded";
- case BluetoothDevice.BOND_BONDING:
- return "bonding";
- case BluetoothDevice.BOND_BONDED:
- return "bonded";
- default:
- return "??????";
- }
- }
-
- public synchronized boolean setName(String name) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (name == null) {
- return false;
- }
- return setPropertyString("Name", name);
- }
-
- //TODO(): setPropertyString, setPropertyInteger, setPropertyBoolean
- // Either have a single property function with Object as the parameter
- // or have a function for each property and then obfuscate in the JNI layer.
- // The following looks dirty.
- private boolean setPropertyString(String key, String value) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!isEnabledInternal()) return false;
- return setAdapterPropertyStringNative(key, value);
- }
-
- private boolean setPropertyInteger(String key, int value) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!isEnabledInternal()) return false;
- return setAdapterPropertyIntegerNative(key, value);
- }
-
- private boolean setPropertyBoolean(String key, boolean value) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!isEnabledInternal()) return false;
- return setAdapterPropertyBooleanNative(key, value ? 1 : 0);
- }
-
- /**
- * Set the discoverability window for the device. A timeout of zero
- * makes the device permanently discoverable (if the device is
- * discoverable). Setting the timeout to a nonzero value does not make
- * a device discoverable; you need to call setMode() to make the device
- * explicitly discoverable.
- *
- * @param timeout The discoverable timeout in seconds.
- */
- public synchronized boolean setDiscoverableTimeout(int timeout) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- return setPropertyInteger("DiscoverableTimeout", timeout);
- }
-
- public synchronized boolean setScanMode(int mode, int duration) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
- "Need WRITE_SECURE_SETTINGS permission");
- boolean pairable;
- boolean discoverable;
-
- switch (mode) {
- case BluetoothAdapter.SCAN_MODE_NONE:
- pairable = false;
- discoverable = false;
- break;
- case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
- pairable = true;
- discoverable = false;
- break;
- case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
- pairable = true;
- discoverable = true;
- if (DBG) Log.d(TAG, "BT Discoverable for " + duration + " seconds");
- break;
- default:
- Log.w(TAG, "Requested invalid scan mode " + mode);
- return false;
- }
-
- setPropertyBoolean("Discoverable", discoverable);
- setPropertyBoolean("Pairable", pairable);
- return true;
- }
-
- /**
- * @param on true set the local Bluetooth module to be connectable
- * The dicoverability is recovered to what it was before
- * switchConnectable(false) call
- * false set the local Bluetooth module to be not connectable
- * and not dicoverable
- */
- /*package*/ synchronized void switchConnectable(boolean on) {
- setAdapterPropertyBooleanNative("Powered", on ? 1 : 0);
- }
-
- /*package*/ synchronized void setPairable() {
- String pairableString = getProperty("Pairable", false);
- if (pairableString == null) {
- Log.e(TAG, "null pairableString");
- return;
- }
- if (pairableString.equals("false")) {
- setAdapterPropertyBooleanNative("Pairable", 1);
- }
- }
-
- /*package*/ String getProperty(String name, boolean checkState) {
- // If checkState is false, check if the event loop is running.
- // before making the call to Bluez
- if (checkState) {
- if (!isEnabledInternal()) return null;
- } else if (!mEventLoop.isEventLoopRunning()) {
- return null;
- }
-
- return mAdapterProperties.getProperty(name);
- }
-
- BluetoothAdapterProperties getAdapterProperties() {
- return mAdapterProperties;
- }
-
- BluetoothDeviceProperties getDeviceProperties() {
- return mDeviceProperties;
- }
-
- boolean isRemoteDeviceInCache(String address) {
- return mDeviceProperties.isInCache(address);
- }
-
- void setRemoteDeviceProperty(String address, String name, String value) {
- mDeviceProperties.setProperty(address, name, value);
- }
-
- void updateRemoteDevicePropertiesCache(String address) {
- mDeviceProperties.updateCache(address);
- }
-
- public synchronized String getAddress() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- // Don't check state since we want to provide address, even if BT is off
- return getProperty("Address", false);
- }
-
- public synchronized String getName() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- // Don't check state since we want to provide name, even if BT is off
- return getProperty("Name", false);
- }
-
- public ParcelUuid[] getUuids() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- String value = getProperty("UUIDs", true);
- if (value == null) return null;
- return convertStringToParcelUuid(value);
- }
-
- private ParcelUuid[] convertStringToParcelUuid(String value) {
- String[] uuidStrings = null;
- // The UUIDs are stored as a "," separated string.
- uuidStrings = value.split(",");
- ParcelUuid[] uuids = new ParcelUuid[uuidStrings.length];
-
- for (int i = 0; i < uuidStrings.length; i++) {
- uuids[i] = ParcelUuid.fromString(uuidStrings[i]);
- }
- return uuids;
- }
-
- /**
- * Returns the user-friendly name of a remote device. This value is
- * returned from our local cache, which is updated when onPropertyChange
- * event is received.
- * Do not expect to retrieve the updated remote name immediately after
- * changing the name on the remote device.
- *
- * @param address Bluetooth address of remote device.
- *
- * @return The user-friendly name of the specified remote device.
- */
- public synchronized String getRemoteName(String address) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return null;
- }
- return mDeviceProperties.getProperty(address, "Name");
- }
-
- /**
- * Returns alias of a remote device. This value is returned from our
- * local cache, which is updated when onPropertyChange event is received.
- *
- * @param address Bluetooth address of remote device.
- *
- * @return The alias of the specified remote device.
- */
- public synchronized String getRemoteAlias(String address) {
-
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return null;
- }
- return mDeviceProperties.getProperty(address, "Alias");
- }
-
- /**
- * Set the alias of a remote device.
- *
- * @param address Bluetooth address of remote device.
- * @param alias new alias to change to
- * @return true on success, false on error
- */
- public synchronized boolean setRemoteAlias(String address, String alias) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return false;
- }
-
- return setDevicePropertyStringNative(getObjectPathFromAddress(address),
- "Alias", alias);
- }
-
- /**
- * Get the discoverability window for the device. A timeout of zero
- * means that the device is permanently discoverable (if the device is
- * in the discoverable mode).
- *
- * @return The discoverability window of the device, in seconds. A negative
- * value indicates an error.
- */
- public int getDiscoverableTimeout() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- String timeout = getProperty("DiscoverableTimeout", true);
- if (timeout != null)
- return Integer.valueOf(timeout);
- else
- return -1;
- }
-
- public int getScanMode() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!isEnabledInternal())
- return BluetoothAdapter.SCAN_MODE_NONE;
-
- boolean pairable = getProperty("Pairable", true).equals("true");
- boolean discoverable = getProperty("Discoverable", true).equals("true");
- return bluezStringToScanMode (pairable, discoverable);
- }
-
- public synchronized boolean startDiscovery() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (!isEnabledInternal()) return false;
-
- return startDiscoveryNative();
- }
-
- public synchronized boolean cancelDiscovery() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (!isEnabledInternal()) return false;
-
- return stopDiscoveryNative();
- }
-
- public boolean isDiscovering() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- String discoveringProperty = getProperty("Discovering", false);
- if (discoveringProperty == null) {
- return false;
- }
-
- return discoveringProperty.equals("true");
- }
-
- private boolean isBondingFeasible(String address) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (!isEnabledInternal()) return false;
-
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return false;
- }
- address = address.toUpperCase();
-
- if (mBondState.getPendingOutgoingBonding() != null) {
- Log.d(TAG, "Ignoring createBond(): another device is bonding");
- // a different device is currently bonding, fail
- return false;
- }
-
- // Check for bond state only if we are not performing auto
- // pairing exponential back-off attempts.
- if (!mBondState.isAutoPairingAttemptsInProgress(address) &&
- mBondState.getBondState(address) != BluetoothDevice.BOND_NONE) {
- Log.d(TAG, "Ignoring createBond(): this device is already bonding or bonded");
- return false;
- }
-
- if (address.equals(mDockAddress)) {
- if (!writeDockPin()) {
- Log.e(TAG, "Error while writing Pin for the dock");
- return false;
- }
- }
- return true;
- }
-
- public synchronized boolean createBond(String address) {
- if (!isBondingFeasible(address)) return false;
-
- if (!createPairedDeviceNative(address, 60000 /*1 minute*/ )) {
- return false;
- }
-
- mBondState.setPendingOutgoingBonding(address);
- mBondState.setBondState(address, BluetoothDevice.BOND_BONDING);
-
- return true;
- }
-
- public synchronized boolean createBondOutOfBand(String address, byte[] hash,
- byte[] randomizer) {
- if (!isBondingFeasible(address)) return false;
-
- if (!createPairedDeviceOutOfBandNative(address, 60000 /* 1 minute */)) {
- return false;
- }
-
- setDeviceOutOfBandData(address, hash, randomizer);
- mBondState.setPendingOutgoingBonding(address);
- mBondState.setBondState(address, BluetoothDevice.BOND_BONDING);
-
- return true;
- }
-
- public synchronized boolean setDeviceOutOfBandData(String address, byte[] hash,
- byte[] randomizer) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (!isEnabledInternal()) return false;
-
- Pair <byte[], byte[]> value = new Pair<byte[], byte[]>(hash, randomizer);
-
- if (DBG) {
- Log.d(TAG, "Setting out of band data for: " + address + ":" +
- Arrays.toString(hash) + ":" + Arrays.toString(randomizer));
- }
-
- mDeviceOobData.put(address, value);
- return true;
- }
-
- Pair<byte[], byte[]> getDeviceOutOfBandData(BluetoothDevice device) {
- return mDeviceOobData.get(device.getAddress());
- }
-
-
- public synchronized byte[] readOutOfBandData() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
- if (!isEnabledInternal()) return null;
-
- return readAdapterOutOfBandDataNative();
- }
-
- public synchronized boolean cancelBondProcess(String address) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (!isEnabledInternal()) return false;
-
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return false;
- }
- address = address.toUpperCase();
- if (mBondState.getBondState(address) != BluetoothDevice.BOND_BONDING) {
- return false;
- }
-
- mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
- BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
- cancelDeviceCreationNative(address);
- return true;
- }
-
- public synchronized boolean removeBond(String address) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (!isEnabledInternal()) return false;
-
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return false;
- }
- BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
- if (state != null) {
- state.sendMessage(BluetoothDeviceProfileState.UNPAIR);
- return true;
- } else {
- return false;
- }
- }
-
- public synchronized boolean removeBondInternal(String address) {
- // Unset the trusted device state and then unpair
- setTrust(address, false);
- return removeDeviceNative(getObjectPathFromAddress(address));
- }
-
- public synchronized String[] listBonds() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return mBondState.listInState(BluetoothDevice.BOND_BONDED);
- }
-
- /*package*/ synchronized String[] listInState(int state) {
- return mBondState.listInState(state);
- }
-
- public synchronized int getBondState(String address) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return BluetoothDevice.ERROR;
- }
- return mBondState.getBondState(address.toUpperCase());
- }
-
- /*package*/ synchronized boolean setBondState(String address, int state) {
- return setBondState(address, state, 0);
- }
-
- /*package*/ synchronized boolean setBondState(String address, int state, int reason) {
- mBondState.setBondState(address.toUpperCase(), state, reason);
- return true;
- }
-
- public synchronized boolean isBluetoothDock(String address) {
- SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
- Context.MODE_PRIVATE);
-
- return sp.contains(SHARED_PREFERENCE_DOCK_ADDRESS + address);
- }
-
- /*package*/ String[] getRemoteDeviceProperties(String address) {
- if (!isEnabledInternal()) return null;
-
- String objectPath = getObjectPathFromAddress(address);
- return (String [])getDevicePropertiesNative(objectPath);
- }
-
- /**
- * Sets the remote device trust state.
- *
- * @return boolean to indicate operation success or fail
- */
- public synchronized boolean setTrust(String address, boolean value) {
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- return false;
- }
-
- if (!isEnabledInternal()) return false;
-
- return setDevicePropertyBooleanNative(
- getObjectPathFromAddress(address), "Trusted", value ? 1 : 0);
- }
-
- /**
- * Gets the remote device trust state as boolean.
- * Note: this value may be
- * retrieved from cache if we retrieved the data before *
- *
- * @return boolean to indicate trusted or untrusted state
- */
- public synchronized boolean getTrustState(String address) {
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return false;
- }
-
- String val = mDeviceProperties.getProperty(address, "Trusted");
- if (val == null) {
- return false;
- } else {
- return val.equals("true");
- }
- }
-
- /**
- * Gets the remote major, minor classes encoded as a 32-bit
- * integer.
- *
- * Note: this value is retrieved from cache, because we get it during
- * remote-device discovery.
- *
- * @return 32-bit integer encoding the remote major, minor, and service
- * classes.
- */
- public synchronized int getRemoteClass(String address) {
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- return BluetoothClass.ERROR;
- }
- String val = mDeviceProperties.getProperty(address, "Class");
- if (val == null)
- return BluetoothClass.ERROR;
- else {
- return Integer.valueOf(val);
- }
- }
-
-
- /**
- * Gets the UUIDs supported by the remote device
- *
- * @return array of 128bit ParcelUuids
- */
- public synchronized ParcelUuid[] getRemoteUuids(String address) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return null;
- }
- return getUuidFromCache(address);
- }
-
- ParcelUuid[] getUuidFromCache(String address) {
- String value = mDeviceProperties.getProperty(address, "UUIDs");
- if (value == null) return null;
-
- String[] uuidStrings = null;
- // The UUIDs are stored as a "," separated string.
- uuidStrings = value.split(",");
- ParcelUuid[] uuids = new ParcelUuid[uuidStrings.length];
-
- for (int i = 0; i < uuidStrings.length; i++) {
- uuids[i] = ParcelUuid.fromString(uuidStrings[i]);
- }
- return uuids;
- }
-
- /**
- * Connect and fetch new UUID's using SDP.
- * The UUID's found are broadcast as intents.
- * Optionally takes a uuid and callback to fetch the RFCOMM channel for the
- * a given uuid.
- * TODO: Don't wait UUID_INTENT_DELAY to broadcast UUID intents on success
- * TODO: Don't wait UUID_INTENT_DELAY to handle the failure case for
- * callback and broadcast intents.
- */
- public synchronized boolean fetchRemoteUuids(String address, ParcelUuid uuid,
- IBluetoothCallback callback) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!isEnabledInternal()) return false;
-
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return false;
- }
-
- RemoteService service = new RemoteService(address, uuid);
- if (uuid != null && mUuidCallbackTracker.get(service) != null) {
- // An SDP query for this address & uuid is already in progress
- // Do not add this callback for the uuid
- return false;
- }
-
- if (mUuidIntentTracker.contains(address)) {
- // An SDP query for this address is already in progress
- // Add this uuid onto the in-progress SDP query
- if (uuid != null) {
- mUuidCallbackTracker.put(new RemoteService(address, uuid), callback);
- }
- return true;
- }
-
- // If the device is already created, we will
- // do the SDP on the callback of createDeviceNative.
- boolean ret= createDeviceNative(address);
-
- mUuidIntentTracker.add(address);
- if (uuid != null) {
- mUuidCallbackTracker.put(new RemoteService(address, uuid), callback);
- }
-
- Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT);
- message.obj = address;
- mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY);
- return ret;
- }
-
- /**
- * Gets the rfcomm channel associated with the UUID.
- * Pulls records from the cache only.
- *
- * @param address Address of the remote device
- * @param uuid ParcelUuid of the service attribute
- *
- * @return rfcomm channel associated with the service attribute
- * -1 on error
- */
- public int getRemoteServiceChannel(String address, ParcelUuid uuid) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!isEnabledInternal()) return -1;
-
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return BluetoothDevice.ERROR;
- }
- // Check if we are recovering from a crash.
- if (mDeviceProperties.isEmpty()) {
- if (mDeviceProperties.updateCache(address) == null)
- return -1;
- }
-
- Map<ParcelUuid, Integer> value = mDeviceServiceChannelCache.get(address);
- if (value != null && value.containsKey(uuid))
- return value.get(uuid);
- return -1;
- }
-
- public synchronized boolean setPin(String address, byte[] pin) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (!isEnabledInternal()) return false;
-
- if (pin == null || pin.length <= 0 || pin.length > 16 ||
- !BluetoothAdapter.checkBluetoothAddress(address)) {
- return false;
- }
- address = address.toUpperCase();
- Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
- if (data == null) {
- Log.w(TAG, "setPin(" + address + ") called but no native data available, " +
- "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
- " or by bluez.\n");
- return false;
- }
- // bluez API wants pin as a string
- String pinString;
- try {
- pinString = new String(pin, "UTF8");
- } catch (UnsupportedEncodingException uee) {
- Log.e(TAG, "UTF8 not supported?!?");
- return false;
- }
- return setPinNative(address, pinString, data.intValue());
- }
-
- public synchronized boolean setPasskey(String address, int passkey) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (!isEnabledInternal()) return false;
-
- if (passkey < 0 || passkey > 999999 || !BluetoothAdapter.checkBluetoothAddress(address)) {
- return false;
- }
- address = address.toUpperCase();
- Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
- if (data == null) {
- Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " +
- "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
- " or by bluez.\n");
- return false;
- }
- return setPasskeyNative(address, passkey, data.intValue());
- }
-
- public synchronized boolean setPairingConfirmation(String address, boolean confirm) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (!isEnabledInternal()) return false;
-
- address = address.toUpperCase();
- Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
- if (data == null) {
- Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " +
- "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
- " or by bluez.\n");
- return false;
- }
- return setPairingConfirmationNative(address, confirm, data.intValue());
- }
-
- public synchronized boolean setRemoteOutOfBandData(String address) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (!isEnabledInternal()) return false;
- address = address.toUpperCase();
- Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
- if (data == null) {
- Log.w(TAG, "setRemoteOobData(" + address + ") called but no native data available, " +
- "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
- " or by bluez.\n");
- return false;
- }
-
- Pair<byte[], byte[]> val = mDeviceOobData.get(address);
- byte[] hash, randomizer;
- if (val == null) {
- // TODO: check what should be passed in this case.
- hash = new byte[16];
- randomizer = new byte[16];
- } else {
- hash = val.first;
- randomizer = val.second;
- }
- return setRemoteOutOfBandDataNative(address, hash, randomizer, data.intValue());
- }
-
- public synchronized boolean cancelPairingUserInput(String address) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (!isEnabledInternal()) return false;
-
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return false;
- }
- mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
- BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
- address = address.toUpperCase();
- Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
- if (data == null) {
- Log.w(TAG, "cancelUserInputNative(" + address + ") called but no native data " +
- "available, ignoring. Maybe the PasskeyAgent Request was already cancelled " +
- "by the remote or by bluez.\n");
- return false;
- }
- return cancelPairingUserInputNative(address, data.intValue());
- }
-
- /*package*/ void updateDeviceServiceChannelCache(String address) {
- if (DBG) Log.d(TAG, "updateDeviceServiceChannelCache(" + address + ")");
-
- // We are storing the rfcomm channel numbers only for the uuids
- // we are interested in.
- ParcelUuid[] deviceUuids = getRemoteUuids(address);
-
- ArrayList<ParcelUuid> applicationUuids = new ArrayList<ParcelUuid>();
-
- synchronized (this) {
- for (RemoteService service : mUuidCallbackTracker.keySet()) {
- if (service.address.equals(address)) {
- applicationUuids.add(service.uuid);
- }
- }
- }
-
- Map <ParcelUuid, Integer> uuidToChannelMap = new HashMap<ParcelUuid, Integer>();
-
- // Retrieve RFCOMM channel for default uuids
- for (ParcelUuid uuid : RFCOMM_UUIDS) {
- if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
- int channel = getDeviceServiceChannelForUuid(address, uuid);
- uuidToChannelMap.put(uuid, channel);
- if (DBG) Log.d(TAG, "\tuuid(system): " + uuid + " " + channel);
- }
- }
- // Retrieve RFCOMM channel for application requested uuids
- for (ParcelUuid uuid : applicationUuids) {
- if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
- int channel = getDeviceServiceChannelForUuid(address, uuid);
- uuidToChannelMap.put(uuid, channel);
- if (DBG) Log.d(TAG, "\tuuid(application): " + uuid + " " + channel);
- }
- }
-
- synchronized (this) {
- // Make application callbacks
- for (Iterator<RemoteService> iter = mUuidCallbackTracker.keySet().iterator();
- iter.hasNext();) {
- RemoteService service = iter.next();
- if (service.address.equals(address)) {
- if (uuidToChannelMap.containsKey(service.uuid)) {
- int channel = uuidToChannelMap.get(service.uuid);
-
- if (DBG) Log.d(TAG, "Making callback for " + service.uuid +
- " with result " + channel);
- IBluetoothCallback callback = mUuidCallbackTracker.get(service);
- if (callback != null) {
- try {
- callback.onRfcommChannelFound(channel);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
- }
-
- iter.remove();
- }
- }
- }
-
- // Update cache
- mDeviceServiceChannelCache.put(address, uuidToChannelMap);
- }
- }
-
- private int getDeviceServiceChannelForUuid(String address,
- ParcelUuid uuid) {
- return getDeviceServiceChannelNative(getObjectPathFromAddress(address),
- uuid.toString(), 0x0004);
- }
-
- /**
- * b is a handle to a Binder instance, so that this service can be notified
- * for Applications that terminate unexpectedly, to clean there service
- * records
- */
- public synchronized int addRfcommServiceRecord(String serviceName, ParcelUuid uuid,
- int channel, IBinder b) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (!isEnabledInternal()) return -1;
-
- if (serviceName == null || uuid == null || channel < 1 ||
- channel > BluetoothSocket.MAX_RFCOMM_CHANNEL) {
- return -1;
- }
- if (BluetoothUuid.isUuidPresent(BluetoothUuid.RESERVED_UUIDS, uuid)) {
- Log.w(TAG, "Attempted to register a reserved UUID: " + uuid);
- return -1;
- }
- int handle = addRfcommServiceRecordNative(serviceName,
- uuid.getUuid().getMostSignificantBits(), uuid.getUuid().getLeastSignificantBits(),
- (short)channel);
- if (DBG) Log.d(TAG, "new handle " + Integer.toHexString(handle));
- if (handle == -1) {
- return -1;
- }
-
- ServiceRecordClient client = new ServiceRecordClient();
- client.pid = Binder.getCallingPid();
- client.binder = b;
- client.death = new Reaper(handle, client.pid, RFCOMM_RECORD_REAPER);
- mServiceRecordToPid.put(new Integer(handle), client);
- try {
- b.linkToDeath(client.death, 0);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- client.death = null;
- }
- return handle;
- }
-
- public void removeServiceRecord(int handle) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
- // Since this is a binder call check if Bluetooth is off
- if (getBluetoothStateInternal() == BluetoothAdapter.STATE_OFF) return;
- Message message = mHandler.obtainMessage(MESSAGE_REMOVE_SERVICE_RECORD);
- message.obj = new Pair<Integer, Integer>(handle, Binder.getCallingPid());
- mHandler.sendMessage(message);
- }
-
- private synchronized void checkAndRemoveRecord(int handle, int pid) {
- ServiceRecordClient client = mServiceRecordToPid.get(handle);
- if (client != null && pid == client.pid) {
- if (DBG) Log.d(TAG, "Removing service record " +
- Integer.toHexString(handle) + " for pid " + pid);
-
- if (client.death != null) {
- client.binder.unlinkToDeath(client.death, 0);
- }
-
- mServiceRecordToPid.remove(handle);
- removeServiceRecordNative(handle);
- }
- }
-
- private class Reaper implements IBinder.DeathRecipient {
- int mPid;
- int mHandle;
- int mType;
-
- Reaper(int handle, int pid, int type) {
- mPid = pid;
- mHandle = handle;
- mType = type;
- }
-
- Reaper(int pid, int type) {
- mPid = pid;
- mType = type;
- }
-
- @Override
- public void binderDied() {
- synchronized (BluetoothService.this) {
- if (DBG) Log.d(TAG, "Tracked app " + mPid + " died" + "Type:" + mType);
- if (mType == RFCOMM_RECORD_REAPER) {
- checkAndRemoveRecord(mHandle, mPid);
- } else if (mType == STATE_CHANGE_REAPER) {
- mStateChangeTracker.remove(mPid);
- }
- }
- }
- }
-
-
- @Override
- public boolean changeApplicationBluetoothState(boolean on,
- IBluetoothStateChangeCallback callback, IBinder binder) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- int pid = Binder.getCallingPid();
- //mStateChangeTracker is a synchronized map
- if (!mStateChangeTracker.containsKey(pid)) {
- if (on) {
- mStateChangeTracker.put(pid, callback);
- } else {
- return false;
- }
- } else if (!on) {
- mStateChangeTracker.remove(pid);
- }
-
- if (binder != null) {
- try {
- binder.linkToDeath(new Reaper(pid, STATE_CHANGE_REAPER), 0);
- } catch (RemoteException e) {
- Log.e(TAG, "", e);
- return false;
- }
- }
-
- int type;
- if (on) {
- type = BluetoothAdapterStateMachine.PER_PROCESS_TURN_ON;
- } else {
- type = BluetoothAdapterStateMachine.PER_PROCESS_TURN_OFF;
- }
-
- mBluetoothState.sendMessage(type, callback);
- return true;
- }
-
- boolean isApplicationStateChangeTrackerEmpty() {
- return mStateChangeTracker.isEmpty();
- }
-
- void clearApplicationStateChangeTracker() {
- mStateChangeTracker.clear();
- }
-
- Collection<IBluetoothStateChangeCallback> getApplicationStateChangeCallbacks() {
- return mStateChangeTracker.values();
- }
-
- int getNumberOfApplicationStateChangeTrackers() {
- return mStateChangeTracker.size();
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent == null) return;
-
- String action = intent.getAction();
- if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
- ContentResolver resolver = context.getContentResolver();
- // Query the airplane mode from Settings.System just to make sure that
- // some random app is not sending this intent and disabling bluetooth
- if (isAirplaneModeOn()) {
- mBluetoothState.sendMessage(BluetoothAdapterStateMachine.AIRPLANE_MODE_ON);
- } else {
- mBluetoothState.sendMessage(BluetoothAdapterStateMachine.AIRPLANE_MODE_OFF);
- }
- } else if (Intent.ACTION_DOCK_EVENT.equals(action)) {
- int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
- Intent.EXTRA_DOCK_STATE_UNDOCKED);
- if (DBG) Log.v(TAG, "Received ACTION_DOCK_EVENT with State:" + state);
- if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- mDockAddress = null;
- mDockPin = null;
- } else {
- SharedPreferences.Editor editor =
- mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
- mContext.MODE_PRIVATE).edit();
- editor.putBoolean(SHARED_PREFERENCE_DOCK_ADDRESS + mDockAddress, true);
- editor.apply();
- }
- }
- }
- };
-
- private void registerForAirplaneMode(IntentFilter filter) {
- final ContentResolver resolver = mContext.getContentResolver();
- final String airplaneModeRadios = Settings.System.getString(resolver,
- Settings.System.AIRPLANE_MODE_RADIOS);
- final String toggleableRadios = Settings.System.getString(resolver,
- Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
-
- mIsAirplaneSensitive = airplaneModeRadios == null ? true :
- airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
- mIsAirplaneToggleable = toggleableRadios == null ? false :
- toggleableRadios.contains(Settings.System.RADIO_BLUETOOTH);
-
- if (mIsAirplaneSensitive) {
- filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- }
- }
-
- /* Returns true if airplane mode is currently on */
- /*package*/ final boolean isAirplaneModeOn() {
- return Settings.System.getInt(mContext.getContentResolver(),
- Settings.System.AIRPLANE_MODE_ON, 0) == 1;
- }
-
- /* Broadcast the Uuid intent */
- /*package*/ synchronized void sendUuidIntent(String address) {
- ParcelUuid[] uuid = getUuidFromCache(address);
- Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
- intent.putExtra(BluetoothDevice.EXTRA_UUID, uuid);
- mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
- mUuidIntentTracker.remove(address);
- }
-
- /*package*/ synchronized void makeServiceChannelCallbacks(String address) {
- for (Iterator<RemoteService> iter = mUuidCallbackTracker.keySet().iterator();
- iter.hasNext();) {
- RemoteService service = iter.next();
- if (service.address.equals(address)) {
- if (DBG) Log.d(TAG, "Cleaning up failed UUID channel lookup: "
- + service.address + " " + service.uuid);
- IBluetoothCallback callback = mUuidCallbackTracker.get(service);
- if (callback != null) {
- try {
- callback.onRfcommChannelFound(-1);
- } catch (RemoteException e) {Log.e(TAG, "", e);}
- }
-
- iter.remove();
- }
- }
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
-
- if (getBluetoothStateInternal() != BluetoothAdapter.STATE_ON) {
- return;
- }
-
- pw.println("mIsAirplaneSensitive = " + mIsAirplaneSensitive);
- pw.println("mIsAirplaneToggleable = " + mIsAirplaneToggleable);
-
- pw.println("Local address = " + getAddress());
- pw.println("Local name = " + getName());
- pw.println("isDiscovering() = " + isDiscovering());
-
- mAdapter.getProfileProxy(mContext,
- mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);
- mAdapter.getProfileProxy(mContext,
- mBluetoothProfileServiceListener, BluetoothProfile.INPUT_DEVICE);
- mAdapter.getProfileProxy(mContext,
- mBluetoothProfileServiceListener, BluetoothProfile.PAN);
-
- dumpKnownDevices(pw);
- dumpAclConnectedDevices(pw);
- dumpHeadsetService(pw);
- dumpInputDeviceProfile(pw);
- dumpPanProfile(pw);
- dumpApplicationServiceRecords(pw);
- dumpProfileState(pw);
- }
-
- private void dumpProfileState(PrintWriter pw) {
- pw.println("\n--Profile State dump--");
- pw.println("\n Headset profile state:" +
- mAdapter.getProfileConnectionState(BluetoothProfile.HEADSET));
- pw.println("\n A2dp profile state:" +
- mAdapter.getProfileConnectionState(BluetoothProfile.A2DP));
- pw.println("\n HID profile state:" +
- mAdapter.getProfileConnectionState(BluetoothProfile.INPUT_DEVICE));
- pw.println("\n PAN profile state:" +
- mAdapter.getProfileConnectionState(BluetoothProfile.PAN));
- }
-
- private void dumpHeadsetService(PrintWriter pw) {
- pw.println("\n--Headset Service--");
- if (mHeadsetProxy != null) {
- List<BluetoothDevice> deviceList = mHeadsetProxy.getConnectedDevices();
- if (deviceList.size() == 0) {
- pw.println("No headsets connected");
- } else {
- BluetoothDevice device = deviceList.get(0);
- pw.println("\ngetConnectedDevices[0] = " + device);
- dumpHeadsetConnectionState(pw, device);
- pw.println("getBatteryUsageHint() = " +
- mHeadsetProxy.getBatteryUsageHint(device));
- }
-
- deviceList.clear();
- deviceList = mHeadsetProxy.getDevicesMatchingConnectionStates(new int[] {
- BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED});
- pw.println("--Connected and Disconnected Headsets");
- for (BluetoothDevice device: deviceList) {
- pw.println(device);
- if (mHeadsetProxy.isAudioConnected(device)) {
- pw.println("SCO audio connected to device:" + device);
- }
- }
- }
- }
-
- private void dumpInputDeviceProfile(PrintWriter pw) {
- pw.println("\n--Bluetooth Service- Input Device Profile");
- if (mInputDevice != null) {
- List<BluetoothDevice> deviceList = mInputDevice.getConnectedDevices();
- if (deviceList.size() == 0) {
- pw.println("No input devices connected");
- } else {
- pw.println("Number of connected devices:" + deviceList.size());
- BluetoothDevice device = deviceList.get(0);
- pw.println("getConnectedDevices[0] = " + device);
- pw.println("Priority of Connected device = " + mInputDevice.getPriority(device));
-
- switch (mInputDevice.getConnectionState(device)) {
- case BluetoothInputDevice.STATE_CONNECTING:
- pw.println("getConnectionState() = STATE_CONNECTING");
- break;
- case BluetoothInputDevice.STATE_CONNECTED:
- pw.println("getConnectionState() = STATE_CONNECTED");
- break;
- case BluetoothInputDevice.STATE_DISCONNECTING:
- pw.println("getConnectionState() = STATE_DISCONNECTING");
- break;
- }
- }
- deviceList.clear();
- deviceList = mInputDevice.getDevicesMatchingConnectionStates(new int[] {
- BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED});
- pw.println("--Connected and Disconnected input devices");
- for (BluetoothDevice device: deviceList) {
- pw.println(device);
- }
- }
- }
-
- private void dumpPanProfile(PrintWriter pw) {
- pw.println("\n--Bluetooth Service- Pan Profile");
- if (mPan != null) {
- List<BluetoothDevice> deviceList = mPan.getConnectedDevices();
- if (deviceList.size() == 0) {
- pw.println("No Pan devices connected");
- } else {
- pw.println("Number of connected devices:" + deviceList.size());
- BluetoothDevice device = deviceList.get(0);
- pw.println("getConnectedDevices[0] = " + device);
-
- switch (mPan.getConnectionState(device)) {
- case BluetoothInputDevice.STATE_CONNECTING:
- pw.println("getConnectionState() = STATE_CONNECTING");
- break;
- case BluetoothInputDevice.STATE_CONNECTED:
- pw.println("getConnectionState() = STATE_CONNECTED");
- break;
- case BluetoothInputDevice.STATE_DISCONNECTING:
- pw.println("getConnectionState() = STATE_DISCONNECTING");
- break;
- }
- }
- deviceList.clear();
- deviceList = mPan.getDevicesMatchingConnectionStates(new int[] {
- BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED});
- pw.println("--Connected and Disconnected Pan devices");
- for (BluetoothDevice device: deviceList) {
- pw.println(device);
- }
- }
- }
-
- private void dumpHeadsetConnectionState(PrintWriter pw,
- BluetoothDevice device) {
- switch (mHeadsetProxy.getConnectionState(device)) {
- case BluetoothHeadset.STATE_CONNECTING:
- pw.println("getConnectionState() = STATE_CONNECTING");
- break;
- case BluetoothHeadset.STATE_CONNECTED:
- pw.println("getConnectionState() = STATE_CONNECTED");
- break;
- case BluetoothHeadset.STATE_DISCONNECTING:
- pw.println("getConnectionState() = STATE_DISCONNECTING");
- break;
- case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- pw.println("getConnectionState() = STATE_AUDIO_CONNECTED");
- break;
- }
- }
-
- private void dumpApplicationServiceRecords(PrintWriter pw) {
- pw.println("\n--Application Service Records--");
- for (Integer handle : mServiceRecordToPid.keySet()) {
- Integer pid = mServiceRecordToPid.get(handle).pid;
- pw.println("\tpid " + pid + " handle " + Integer.toHexString(handle));
- }
- }
-
- private void dumpAclConnectedDevices(PrintWriter pw) {
- String[] devicesObjectPath = getKnownDevices();
- pw.println("\n--ACL connected devices--");
- if (devicesObjectPath != null) {
- for (String device : devicesObjectPath) {
- pw.println(getAddressFromObjectPath(device));
- }
- }
- }
-
- private void dumpKnownDevices(PrintWriter pw) {
- pw.println("\n--Known devices--");
- for (String address : mDeviceProperties.keySet()) {
- int bondState = mBondState.getBondState(address);
- pw.printf("%s %10s (%d) %s\n", address,
- toBondStateString(bondState),
- mBondState.getAttempt(address),
- getRemoteName(address));
-
- Map<ParcelUuid, Integer> uuidChannels = mDeviceServiceChannelCache.get(address);
- if (uuidChannels == null) {
- pw.println("\tuuids = null");
- } else {
- for (ParcelUuid uuid : uuidChannels.keySet()) {
- Integer channel = uuidChannels.get(uuid);
- if (channel == null) {
- pw.println("\t" + uuid);
- } else {
- pw.println("\t" + uuid + " RFCOMM channel = " + channel);
- }
- }
- }
- for (RemoteService service : mUuidCallbackTracker.keySet()) {
- if (service.address.equals(address)) {
- pw.println("\tPENDING CALLBACK: " + service.uuid);
- }
- }
- }
- }
-
- private void getProfileProxy() {
- mAdapter.getProfileProxy(mContext,
- mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);
- }
-
- private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
- new BluetoothProfile.ServiceListener() {
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- if (profile == BluetoothProfile.HEADSET) {
- mHeadsetProxy = (BluetoothHeadset) proxy;
- } else if (profile == BluetoothProfile.INPUT_DEVICE) {
- mInputDevice = (BluetoothInputDevice) proxy;
- } else if (profile == BluetoothProfile.PAN) {
- mPan = (BluetoothPan) proxy;
- }
- }
- public void onServiceDisconnected(int profile) {
- if (profile == BluetoothProfile.HEADSET) {
- mHeadsetProxy = null;
- } else if (profile == BluetoothProfile.INPUT_DEVICE) {
- mInputDevice = null;
- } else if (profile == BluetoothProfile.PAN) {
- mPan = null;
- }
- }
- };
-
- /* package */ static int bluezStringToScanMode(boolean pairable, boolean discoverable) {
- if (pairable && discoverable)
- return BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
- else if (pairable && !discoverable)
- return BluetoothAdapter.SCAN_MODE_CONNECTABLE;
- else
- return BluetoothAdapter.SCAN_MODE_NONE;
- }
-
- /* package */ static String scanModeToBluezString(int mode) {
- switch (mode) {
- case BluetoothAdapter.SCAN_MODE_NONE:
- return "off";
- case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
- return "connectable";
- case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
- return "discoverable";
- }
- return null;
- }
-
- /*package*/ String getAddressFromObjectPath(String objectPath) {
- String adapterObjectPath = mAdapterProperties.getObjectPath();
- if (adapterObjectPath == null || objectPath == null) {
- Log.e(TAG, "getAddressFromObjectPath: AdapterObjectPath:" + adapterObjectPath +
- " or deviceObjectPath:" + objectPath + " is null");
- return null;
- }
- if (!objectPath.startsWith(adapterObjectPath)) {
- Log.e(TAG, "getAddressFromObjectPath: AdapterObjectPath:" + adapterObjectPath +
- " is not a prefix of deviceObjectPath:" + objectPath +
- "bluetoothd crashed ?");
- return null;
- }
- String address = objectPath.substring(adapterObjectPath.length());
- if (address != null) return address.replace('_', ':');
-
- Log.e(TAG, "getAddressFromObjectPath: Address being returned is null");
- return null;
- }
-
- /*package*/ String getObjectPathFromAddress(String address) {
- String path = mAdapterProperties.getObjectPath();
- if (path == null) {
- Log.e(TAG, "Error: Object Path is null");
- return null;
- }
- path = path + address.replace(":", "_");
- return path;
- }
-
- /*package */ void setLinkTimeout(String address, int num_slots) {
- String path = getObjectPathFromAddress(address);
- boolean result = setLinkTimeoutNative(path, num_slots);
-
- if (!result) Log.d(TAG, "Set Link Timeout to " + num_slots + " slots failed");
- }
-
- /**** Handlers for PAN Profile ****/
- // TODO: This needs to be converted to a state machine.
-
- public boolean isTetheringOn() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mBluetoothPanProfileHandler) {
- return mBluetoothPanProfileHandler.isTetheringOn();
- }
- }
-
- /*package*/boolean allowIncomingTethering() {
- synchronized (mBluetoothPanProfileHandler) {
- return mBluetoothPanProfileHandler.allowIncomingTethering();
- }
- }
-
- public void setBluetoothTethering(boolean value) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mBluetoothPanProfileHandler) {
- mBluetoothPanProfileHandler.setBluetoothTethering(value);
- }
- }
-
- public int getPanDeviceConnectionState(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mBluetoothPanProfileHandler) {
- return mBluetoothPanProfileHandler.getPanDeviceConnectionState(device);
- }
- }
-
- public boolean connectPanDevice(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- synchronized (mBluetoothPanProfileHandler) {
- return mBluetoothPanProfileHandler.connectPanDevice(device);
- }
- }
-
- public List<BluetoothDevice> getConnectedPanDevices() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mBluetoothPanProfileHandler) {
- return mBluetoothPanProfileHandler.getConnectedPanDevices();
- }
- }
-
- public List<BluetoothDevice> getPanDevicesMatchingConnectionStates(
- int[] states) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mBluetoothPanProfileHandler) {
- return mBluetoothPanProfileHandler.getPanDevicesMatchingConnectionStates(states);
- }
- }
-
- public boolean disconnectPanDevice(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- synchronized (mBluetoothPanProfileHandler) {
- return mBluetoothPanProfileHandler.disconnectPanDevice(device);
- }
- }
-
- /*package*/void handlePanDeviceStateChange(BluetoothDevice device,
- String iface,
- int state,
- int role) {
- synchronized (mBluetoothPanProfileHandler) {
- mBluetoothPanProfileHandler.handlePanDeviceStateChange(device, iface, state, role);
- }
- }
-
- /*package*/void handlePanDeviceStateChange(BluetoothDevice device,
- int state, int role) {
- synchronized (mBluetoothPanProfileHandler) {
- mBluetoothPanProfileHandler.handlePanDeviceStateChange(device, null, state, role);
- }
- }
-
- /**** Handlers for Input Device Profile ****/
- // This needs to be converted to state machine
-
- public boolean connectInputDevice(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress());
- synchronized (mBluetoothInputProfileHandler) {
- return mBluetoothInputProfileHandler.connectInputDevice(device, state);
- }
- }
-
- public boolean connectInputDeviceInternal(BluetoothDevice device) {
- synchronized (mBluetoothInputProfileHandler) {
- return mBluetoothInputProfileHandler.connectInputDeviceInternal(device);
- }
- }
-
- public boolean disconnectInputDevice(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- BluetoothDeviceProfileState state = mDeviceProfileState.get(device.getAddress());
- synchronized (mBluetoothInputProfileHandler) {
- return mBluetoothInputProfileHandler.disconnectInputDevice(device, state);
- }
- }
-
- public boolean disconnectInputDeviceInternal(BluetoothDevice device) {
- synchronized (mBluetoothInputProfileHandler) {
- return mBluetoothInputProfileHandler.disconnectInputDeviceInternal(device);
- }
- }
-
- public int getInputDeviceConnectionState(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mBluetoothInputProfileHandler) {
- return mBluetoothInputProfileHandler.getInputDeviceConnectionState(device);
- }
- }
-
- public List<BluetoothDevice> getConnectedInputDevices() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mBluetoothInputProfileHandler) {
- return mBluetoothInputProfileHandler.getConnectedInputDevices();
- }
- }
-
- public List<BluetoothDevice> getInputDevicesMatchingConnectionStates(
- int[] states) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mBluetoothInputProfileHandler) {
- return mBluetoothInputProfileHandler.getInputDevicesMatchingConnectionStates(states);
- }
- }
-
-
- public int getInputDevicePriority(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- synchronized (mBluetoothInputProfileHandler) {
- return mBluetoothInputProfileHandler.getInputDevicePriority(device);
- }
- }
-
- public boolean setInputDevicePriority(BluetoothDevice device, int priority) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- synchronized (mBluetoothInputProfileHandler) {
- return mBluetoothInputProfileHandler.setInputDevicePriority(device, priority);
- }
- }
-
- /**
- * Handle incoming profile acceptance for profiles handled by Bluetooth Service,
- * currently PAN and HID. This also is the catch all for all rejections for profiles
- * that is not supported.
- *
- * @param device - Bluetooth Device
- * @param allow - true / false
- * @return
- */
- public boolean allowIncomingProfileConnect(BluetoothDevice device, boolean allow) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- String address = device.getAddress();
- if (!BluetoothAdapter.checkBluetoothAddress(address)) {
- return false;
- }
-
- Integer data = getAuthorizationAgentRequestData(address);
- if (data == null) {
- Log.w(TAG, "allowIncomingProfileConnect(" + device +
- ") called but no native data available");
- return false;
- }
- if (DBG) log("allowIncomingProfileConnect: " + device + " : " + allow + " : " + data);
- return setAuthorizationNative(address, allow, data.intValue());
- }
-
- /*package*/List<BluetoothDevice> lookupInputDevicesMatchingStates(int[] states) {
- synchronized (mBluetoothInputProfileHandler) {
- return mBluetoothInputProfileHandler.lookupInputDevicesMatchingStates(states);
- }
- }
-
- /*package*/void handleInputDevicePropertyChange(String address, boolean connected) {
- synchronized (mBluetoothInputProfileHandler) {
- mBluetoothInputProfileHandler.handleInputDevicePropertyChange(address, connected);
- }
- }
-
- /**** Handlers for Health Device Profile ****/
- // TODO: All these need to be converted to a state machine.
-
- public boolean registerAppConfiguration(BluetoothHealthAppConfiguration config,
- IBluetoothHealthCallback callback) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
- synchronized (mBluetoothHealthProfileHandler) {
- return mBluetoothHealthProfileHandler.registerAppConfiguration(config, callback);
- }
- }
-
- public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
- synchronized (mBluetoothHealthProfileHandler) {
- return mBluetoothHealthProfileHandler.unregisterAppConfiguration(config);
- }
- }
-
-
- public boolean connectChannelToSource(BluetoothDevice device,
- BluetoothHealthAppConfiguration config) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
- synchronized (mBluetoothHealthProfileHandler) {
- return mBluetoothHealthProfileHandler.connectChannelToSource(device,
- config);
- }
- }
-
- public boolean connectChannelToSink(BluetoothDevice device,
- BluetoothHealthAppConfiguration config, int channelType) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
- synchronized (mBluetoothHealthProfileHandler) {
- return mBluetoothHealthProfileHandler.connectChannel(device, config,
- channelType);
- }
- }
-
- public boolean disconnectChannel(BluetoothDevice device,
- BluetoothHealthAppConfiguration config, int id) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
- synchronized (mBluetoothHealthProfileHandler) {
- return mBluetoothHealthProfileHandler.disconnectChannel(device, config, id);
- }
- }
-
- public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
- BluetoothHealthAppConfiguration config) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
- synchronized (mBluetoothHealthProfileHandler) {
- return mBluetoothHealthProfileHandler.getMainChannelFd(device, config);
- }
- }
-
- /*package*/ void onHealthDevicePropertyChanged(String devicePath,
- String channelPath) {
- synchronized (mBluetoothHealthProfileHandler) {
- mBluetoothHealthProfileHandler.onHealthDevicePropertyChanged(devicePath,
- channelPath);
- }
- }
-
- /*package*/ void onHealthDeviceChannelChanged(String devicePath,
- String channelPath, boolean exists) {
- synchronized(mBluetoothHealthProfileHandler) {
- mBluetoothHealthProfileHandler.onHealthDeviceChannelChanged(devicePath,
- channelPath, exists);
- }
- }
-
- /*package*/ void onHealthDeviceChannelConnectionError(int channelCode,
- int newState) {
- synchronized(mBluetoothHealthProfileHandler) {
- mBluetoothHealthProfileHandler.onHealthDeviceChannelConnectionError(channelCode,
- newState);
- }
- }
-
- public int getHealthDeviceConnectionState(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
- synchronized (mBluetoothHealthProfileHandler) {
- return mBluetoothHealthProfileHandler.getHealthDeviceConnectionState(device);
- }
- }
-
- public List<BluetoothDevice> getConnectedHealthDevices() {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
- synchronized (mBluetoothHealthProfileHandler) {
- return mBluetoothHealthProfileHandler.getConnectedHealthDevices();
- }
- }
-
- public List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(
- int[] states) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
- "Need BLUETOOTH permission");
- synchronized (mBluetoothHealthProfileHandler) {
- return mBluetoothHealthProfileHandler.
- getHealthDevicesMatchingConnectionStates(states);
- }
- }
-
- /*package*/boolean notifyIncomingHidConnection(String address) {
- BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
- if (state == null) {
- return false;
- }
- Message msg = new Message();
- msg.what = BluetoothDeviceProfileState.CONNECT_HID_INCOMING;
- state.sendMessage(msg);
- return true;
- }
-
- public boolean connectHeadset(String address) {
- if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false;
-
- BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
- if (state != null) {
- Message msg = new Message();
- msg.arg1 = BluetoothDeviceProfileState.CONNECT_HFP_OUTGOING;
- msg.obj = state;
- mHfpProfileState.sendMessage(msg);
- return true;
- }
- return false;
- }
-
- public boolean disconnectHeadset(String address) {
- if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false;
-
- BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
- if (state != null) {
- Message msg = new Message();
- msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HFP_OUTGOING;
- msg.obj = state;
- mHfpProfileState.sendMessage(msg);
- return true;
- }
- return false;
- }
-
- public boolean connectSink(String address) {
- if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false;
-
- BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
- if (state != null) {
- Message msg = new Message();
- msg.arg1 = BluetoothDeviceProfileState.CONNECT_A2DP_OUTGOING;
- msg.obj = state;
- mA2dpProfileState.sendMessage(msg);
- return true;
- }
- return false;
- }
-
- public boolean disconnectSink(String address) {
- if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false;
-
- BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
- if (state != null) {
- Message msg = new Message();
- msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_A2DP_OUTGOING;
- msg.obj = state;
- mA2dpProfileState.sendMessage(msg);
- return true;
- }
- return false;
- }
-
- BluetoothDeviceProfileState addProfileState(String address, boolean setTrust) {
- BluetoothDeviceProfileState state =
- new BluetoothDeviceProfileState(mContext, address, this, mA2dpService, setTrust);
- mDeviceProfileState.put(address, state);
- state.start();
- return state;
- }
-
- void removeProfileState(String address) {
- BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
- if (state == null) return;
-
- state.doQuit();
- mDeviceProfileState.remove(address);
- }
-
- String[] getKnownDevices() {
- String[] bonds = null;
- String val = getProperty("Devices", true);
- if (val != null) {
- bonds = val.split(",");
- }
- return bonds;
- }
-
- private void initProfileState() {
- String[] bonds = null;
- String val = getProperty("Devices", false);
- if (val != null) {
- bonds = val.split(",");
- }
- if (bonds == null) {
- return;
- }
- for (String path : bonds) {
- String address = getAddressFromObjectPath(path);
- BluetoothDeviceProfileState state = addProfileState(address, false);
- }
- }
-
- private void autoConnect() {
- synchronized (this) {
- if (!mAllowConnect) {
- Log.d(TAG, "Not auto-connecting devices because of temporary BT on state.");
- return;
- }
- }
-
- String[] bonds = getKnownDevices();
- if (bonds == null) {
- return;
- }
- for (String path : bonds) {
- String address = getAddressFromObjectPath(path);
- BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
- if (state != null) {
- Message msg = new Message();
- msg.what = BluetoothDeviceProfileState.AUTO_CONNECT_PROFILES;
- state.sendMessage(msg);
- }
- }
- }
-
- public boolean notifyIncomingConnection(String address, boolean rejected) {
- synchronized (this) {
- if (!mAllowConnect) {
- Log.d(TAG, "Not allowing incoming connection because of temporary BT on state.");
- return false;
- }
- }
- BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
- if (state != null) {
- Message msg = new Message();
- if (rejected) {
- if (mA2dpService.getPriority(getRemoteDevice(address)) >=
- BluetoothProfile.PRIORITY_ON) {
- msg.what = BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES;
- msg.arg1 = BluetoothDeviceProfileState.CONNECT_A2DP_OUTGOING;
- state.sendMessageDelayed(msg,
- BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES_DELAY);
- }
- } else {
- msg.what = BluetoothDeviceProfileState.CONNECT_HFP_INCOMING;
- state.sendMessage(msg);
- }
- return true;
- }
- return false;
- }
-
- /*package*/ boolean notifyIncomingA2dpConnection(String address, boolean rejected) {
- synchronized (this) {
- if (!mAllowConnect) {
- Log.d(TAG, "Not allowing a2dp connection because of temporary BT on state.");
- return false;
- }
- }
-
- BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
- if (state != null) {
- Message msg = new Message();
- if (rejected) {
- if (mHeadsetProxy.getPriority(getRemoteDevice(address)) >=
- BluetoothProfile.PRIORITY_ON) {
- msg.what = BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES;
- msg.arg1 = BluetoothDeviceProfileState.CONNECT_HFP_OUTGOING;
- state.sendMessageDelayed(msg,
- BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES_DELAY);
- }
- } else {
- msg.what = BluetoothDeviceProfileState.CONNECT_A2DP_INCOMING;
- state.sendMessage(msg);
- }
- return true;
- }
- return false;
- }
-
- /*package*/ void setA2dpService(BluetoothA2dpService a2dpService) {
- mA2dpService = a2dpService;
- }
-
- /*package*/ Integer getAuthorizationAgentRequestData(String address) {
- Integer data = mEventLoop.getAuthorizationAgentRequestData().remove(address);
- return data;
- }
-
- public void sendProfileStateMessage(int profile, int cmd) {
- Message msg = new Message();
- msg.what = cmd;
- if (profile == BluetoothProfileState.HFP) {
- mHfpProfileState.sendMessage(msg);
- } else if (profile == BluetoothProfileState.A2DP) {
- mA2dpProfileState.sendMessage(msg);
- }
- }
-
- public int getAdapterConnectionState() {
- return mAdapterConnectionState;
- }
-
- public int getProfileConnectionState(int profile) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-
- Pair<Integer, Integer> state = mProfileConnectionState.get(profile);
- if (state == null) return BluetoothProfile.STATE_DISCONNECTED;
-
- return state.first;
- }
-
- private void updateProfileConnectionState(int profile, int newState, int oldState) {
- // mProfileConnectionState is a hashmap -
- // <Integer, Pair<Integer, Integer>>
- // The key is the profile, the value is a pair. first element
- // is the state and the second element is the number of devices
- // in that state.
- int numDev = 1;
- int newHashState = newState;
- boolean update = true;
-
- // The following conditions are considered in this function:
- // 1. If there is no record of profile and state - update
- // 2. If a new device's state is current hash state - increment
- // number of devices in the state.
- // 3. If a state change has happened to Connected or Connecting
- // (if current state is not connected), update.
- // 4. If numDevices is 1 and that device state is being updated, update
- // 5. If numDevices is > 1 and one of the devices is changing state,
- // decrement numDevices but maintain oldState if it is Connected or
- // Connecting
- Pair<Integer, Integer> stateNumDev = mProfileConnectionState.get(profile);
- if (stateNumDev != null) {
- int currHashState = stateNumDev.first;
- numDev = stateNumDev.second;
-
- if (newState == currHashState) {
- numDev ++;
- } else if (newState == BluetoothProfile.STATE_CONNECTED ||
- (newState == BluetoothProfile.STATE_CONNECTING &&
- currHashState != BluetoothProfile.STATE_CONNECTED)) {
- numDev = 1;
- } else if (numDev == 1 && oldState == currHashState) {
- update = true;
- } else if (numDev > 1 && oldState == currHashState) {
- numDev --;
-
- if (currHashState == BluetoothProfile.STATE_CONNECTED ||
- currHashState == BluetoothProfile.STATE_CONNECTING) {
- newHashState = currHashState;
- }
- } else {
- update = false;
- }
- }
-
- if (update) {
- mProfileConnectionState.put(profile, new Pair<Integer, Integer>(newHashState,
- numDev));
- }
- }
-
- public synchronized void sendConnectionStateChange(BluetoothDevice
- device, int profile, int state, int prevState) {
- // Since this is a binder call check if Bluetooth is on still
- if (getBluetoothStateInternal() == BluetoothAdapter.STATE_OFF) return;
-
- if (!validateProfileConnectionState(state) ||
- !validateProfileConnectionState(prevState)) {
- // Previously, an invalid state was broadcast anyway,
- // with the invalid state converted to -1 in the intent.
- // Better to log an error and not send an intent with
- // invalid contents or set mAdapterConnectionState to -1.
- Log.e(TAG, "Error in sendConnectionStateChange: "
- + "prevState " + prevState + " state " + state);
- return;
- }
-
- updateProfileConnectionState(profile, state, prevState);
-
- if (updateCountersAndCheckForConnectionStateChange(state, prevState)) {
- mAdapterConnectionState = state;
-
- if (state == BluetoothProfile.STATE_DISCONNECTED) {
- mBluetoothState.sendMessage(BluetoothAdapterStateMachine.ALL_DEVICES_DISCONNECTED);
- }
-
- Intent intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
- intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- intent.putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
- convertToAdapterState(state));
- intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE,
- convertToAdapterState(prevState));
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent, BLUETOOTH_PERM);
- Log.d(TAG, "CONNECTION_STATE_CHANGE: " + device + ": "
- + prevState + " -> " + state);
- }
- }
-
- private boolean validateProfileConnectionState(int state) {
- return (state == BluetoothProfile.STATE_DISCONNECTED ||
- state == BluetoothProfile.STATE_CONNECTING ||
- state == BluetoothProfile.STATE_CONNECTED ||
- state == BluetoothProfile.STATE_DISCONNECTING);
- }
-
- private int convertToAdapterState(int state) {
- switch (state) {
- case BluetoothProfile.STATE_DISCONNECTED:
- return BluetoothAdapter.STATE_DISCONNECTED;
- case BluetoothProfile.STATE_DISCONNECTING:
- return BluetoothAdapter.STATE_DISCONNECTING;
- case BluetoothProfile.STATE_CONNECTED:
- return BluetoothAdapter.STATE_CONNECTED;
- case BluetoothProfile.STATE_CONNECTING:
- return BluetoothAdapter.STATE_CONNECTING;
- }
- Log.e(TAG, "Error in convertToAdapterState");
- return -1;
- }
-
- private boolean updateCountersAndCheckForConnectionStateChange(int state, int prevState) {
- switch (prevState) {
- case BluetoothProfile.STATE_CONNECTING:
- mProfilesConnecting--;
- break;
-
- case BluetoothProfile.STATE_CONNECTED:
- mProfilesConnected--;
- break;
-
- case BluetoothProfile.STATE_DISCONNECTING:
- mProfilesDisconnecting--;
- break;
- }
-
- switch (state) {
- case BluetoothProfile.STATE_CONNECTING:
- mProfilesConnecting++;
- return (mProfilesConnected == 0 && mProfilesConnecting == 1);
-
- case BluetoothProfile.STATE_CONNECTED:
- mProfilesConnected++;
- return (mProfilesConnected == 1);
-
- case BluetoothProfile.STATE_DISCONNECTING:
- mProfilesDisconnecting++;
- return (mProfilesConnected == 0 && mProfilesDisconnecting == 1);
-
- case BluetoothProfile.STATE_DISCONNECTED:
- return (mProfilesConnected == 0 && mProfilesConnecting == 0);
-
- default:
- return true;
- }
- }
-
- private void createIncomingConnectionStateFile() {
- File f = new File(INCOMING_CONNECTION_FILE);
- if (!f.exists()) {
- try {
- f.createNewFile();
- } catch (IOException e) {
- Log.e(TAG, "IOException: cannot create file");
- }
- }
- }
-
- /** @hide */
- public Pair<Integer, String> getIncomingState(String address) {
- if (mIncomingConnections.isEmpty()) {
- createIncomingConnectionStateFile();
- readIncomingConnectionState();
- }
- return mIncomingConnections.get(address);
- }
-
- private void readIncomingConnectionState() {
- synchronized(mIncomingConnections) {
- FileInputStream fstream = null;
- try {
- fstream = new FileInputStream(INCOMING_CONNECTION_FILE);
- DataInputStream in = new DataInputStream(fstream);
- BufferedReader file = new BufferedReader(new InputStreamReader(in));
- String line;
- while((line = file.readLine()) != null) {
- line = line.trim();
- if (line.length() == 0) continue;
- String[] value = line.split(",");
- if (value != null && value.length == 3) {
- Integer val1 = Integer.parseInt(value[1]);
- Pair<Integer, String> val = new Pair(val1, value[2]);
- mIncomingConnections.put(value[0], val);
- }
- }
- } catch (FileNotFoundException e) {
- log("FileNotFoundException: readIncomingConnectionState" + e.toString());
- } catch (IOException e) {
- log("IOException: readIncomingConnectionState" + e.toString());
- } finally {
- if (fstream != null) {
- try {
- fstream.close();
- } catch (IOException e) {
- // Ignore
- }
- }
- }
- }
- }
-
- private void truncateIncomingConnectionFile() {
- RandomAccessFile r = null;
- try {
- r = new RandomAccessFile(INCOMING_CONNECTION_FILE, "rw");
- r.setLength(0);
- } catch (FileNotFoundException e) {
- log("FileNotFoundException: truncateIncomingConnectionState" + e.toString());
- } catch (IOException e) {
- log("IOException: truncateIncomingConnectionState" + e.toString());
- } finally {
- if (r != null) {
- try {
- r.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
- }
-
- /** @hide */
- public void writeIncomingConnectionState(String address, Pair<Integer, String> data) {
- synchronized(mIncomingConnections) {
- mIncomingConnections.put(address, data);
-
- truncateIncomingConnectionFile();
- BufferedWriter out = null;
- StringBuilder value = new StringBuilder();
- try {
- out = new BufferedWriter(new FileWriter(INCOMING_CONNECTION_FILE, true));
- for (String devAddress: mIncomingConnections.keySet()) {
- Pair<Integer, String> val = mIncomingConnections.get(devAddress);
- value.append(devAddress);
- value.append(",");
- value.append(val.first.toString());
- value.append(",");
- value.append(val.second);
- value.append("\n");
- }
- out.write(value.toString());
- } catch (FileNotFoundException e) {
- log("FileNotFoundException: writeIncomingConnectionState" + e.toString());
- } catch (IOException e) {
- log("IOException: writeIncomingConnectionState" + e.toString());
- } finally {
- if (out != null) {
- try {
- out.close();
- } catch (IOException e) {
- // Ignore
- }
- }
- }
- }
- }
-
- private static void log(String msg) {
- Log.d(TAG, msg);
- }
-
- private native static void classInitNative();
- private native void initializeNativeDataNative();
- private native boolean setupNativeDataNative();
- private native boolean tearDownNativeDataNative();
- private native void cleanupNativeDataNative();
- /*package*/ native String getAdapterPathNative();
-
- private native int isEnabledNative();
- /*package*/ native int enableNative();
- /*package*/ native int disableNative();
-
- /*package*/ native Object[] getAdapterPropertiesNative();
- private native Object[] getDevicePropertiesNative(String objectPath);
- private native boolean setAdapterPropertyStringNative(String key, String value);
- private native boolean setAdapterPropertyIntegerNative(String key, int value);
- private native boolean setAdapterPropertyBooleanNative(String key, int value);
-
- private native boolean startDiscoveryNative();
- private native boolean stopDiscoveryNative();
-
- private native boolean createPairedDeviceNative(String address, int timeout_ms);
- private native boolean createPairedDeviceOutOfBandNative(String address, int timeout_ms);
- private native byte[] readAdapterOutOfBandDataNative();
-
- private native boolean cancelDeviceCreationNative(String address);
- private native boolean removeDeviceNative(String objectPath);
- private native int getDeviceServiceChannelNative(String objectPath, String uuid,
- int attributeId);
-
- private native boolean cancelPairingUserInputNative(String address, int nativeData);
- private native boolean setPinNative(String address, String pin, int nativeData);
- private native boolean setPasskeyNative(String address, int passkey, int nativeData);
- private native boolean setPairingConfirmationNative(String address, boolean confirm,
- int nativeData);
- private native boolean setRemoteOutOfBandDataNative(String address, byte[] hash,
- byte[] randomizer, int nativeData);
-
- private native boolean setDevicePropertyBooleanNative(String objectPath, String key,
- int value);
- private native boolean setDevicePropertyStringNative(String objectPath, String key,
- String value);
- private native boolean createDeviceNative(String address);
- /*package*/ native boolean discoverServicesNative(String objectPath, String pattern);
-
- private native int addRfcommServiceRecordNative(String name, long uuidMsb, long uuidLsb,
- short channel);
- private native boolean removeServiceRecordNative(int handle);
- private native boolean setLinkTimeoutNative(String path, int num_slots);
-
- native boolean connectInputDeviceNative(String path);
- native boolean disconnectInputDeviceNative(String path);
-
- native boolean setBluetoothTetheringNative(boolean value, String nap, String bridge);
- native boolean connectPanDeviceNative(String path, String dstRole);
- native boolean disconnectPanDeviceNative(String path);
- native boolean disconnectPanServerDeviceNative(String path,
- String address, String iface);
-
- private native int[] addReservedServiceRecordsNative(int[] uuuids);
- private native boolean removeReservedServiceRecordsNative(int[] handles);
-
- // Health API
- native String registerHealthApplicationNative(int dataType, String role, String name,
- String channelType);
- native String registerHealthApplicationNative(int dataType, String role, String name);
- native boolean unregisterHealthApplicationNative(String path);
- native boolean createChannelNative(String devicePath, String appPath, String channelType,
- int code);
- native boolean destroyChannelNative(String devicePath, String channelpath, int code);
- native String getMainChannelNative(String path);
- native String getChannelApplicationNative(String channelPath);
- native ParcelFileDescriptor getChannelFdNative(String channelPath);
- native boolean releaseChannelFdNative(String channelPath);
- native boolean setAuthorizationNative(String address, boolean value, int data);
-}
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index c783e6a..bc3efdd 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -18,6 +18,9 @@ package android.server.search;
import com.android.internal.content.PackageMonitor;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
import android.app.ISearchManager;
import android.app.SearchManager;
import android.app.SearchableInfo;
@@ -27,11 +30,19 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
+import android.os.Binder;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
import java.util.List;
@@ -48,7 +59,7 @@ public class SearchManagerService extends ISearchManager.Stub {
private final Context mContext;
// This field is initialized lazily in getSearchables(), and then never modified.
- private Searchables mSearchables;
+ private SparseArray<Searchables> mSearchables;
private ContentObserver mGlobalSearchObserver;
@@ -66,14 +77,25 @@ public class SearchManagerService extends ISearchManager.Stub {
mContext.getContentResolver());
}
- private synchronized Searchables getSearchables() {
+ private synchronized Searchables getSearchables(int userId) {
if (mSearchables == null) {
- Log.i(TAG, "Building list of searchable activities");
new MyPackageMonitor().register(mContext, null, true);
- mSearchables = new Searchables(mContext);
- mSearchables.buildSearchableList();
+ mSearchables = new SparseArray<Searchables>();
}
- return mSearchables;
+ Searchables searchables = mSearchables.get(userId);
+
+ long origId = Binder.clearCallingIdentity();
+ boolean userExists = ((UserManager) mContext.getSystemService(Context.USER_SERVICE))
+ .getUserInfo(userId) != null;
+ Binder.restoreCallingIdentity(origId);
+
+ if (searchables == null && userExists) {
+ Log.i(TAG, "Building list of searchable activities for userId=" + userId);
+ searchables = new Searchables(mContext, userId);
+ searchables.buildSearchableList();
+ mSearchables.append(userId, searchables);
+ }
+ return searchables;
}
/**
@@ -87,7 +109,7 @@ public class SearchManagerService extends ISearchManager.Stub {
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mContext.unregisterReceiver(BootCompletedReceiver.this);
- getSearchables();
+ getSearchables(0);
}
}.start();
}
@@ -109,12 +131,16 @@ public class SearchManagerService extends ISearchManager.Stub {
}
private void updateSearchables() {
- // Update list of searchable activities
- getSearchables().buildSearchableList();
+ synchronized (SearchManagerService.this) {
+ // Update list of searchable activities
+ for (int i = 0; i < mSearchables.size(); i++) {
+ getSearchables(mSearchables.keyAt(i)).buildSearchableList();
+ }
+ }
// Inform all listeners that the list of searchables has been updated.
Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
}
@@ -132,10 +158,14 @@ public class SearchManagerService extends ISearchManager.Stub {
@Override
public void onChange(boolean selfChange) {
- getSearchables().buildSearchableList();
+ synchronized (SearchManagerService.this) {
+ for (int i = 0; i < mSearchables.size(); i++) {
+ getSearchables(mSearchables.keyAt(i)).buildSearchableList();
+ }
+ }
Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- mContext.sendBroadcast(intent);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
}
@@ -156,32 +186,76 @@ public class SearchManagerService extends ISearchManager.Stub {
Log.e(TAG, "getSearchableInfo(), activity == null");
return null;
}
- return getSearchables().getSearchableInfo(launchActivity);
+ return getSearchables(UserHandle.getCallingUserId()).getSearchableInfo(launchActivity);
}
/**
* Returns a list of the searchable activities that can be included in global search.
*/
public List<SearchableInfo> getSearchablesInGlobalSearch() {
- return getSearchables().getSearchablesInGlobalSearchList();
+ return getSearchables(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList();
}
public List<ResolveInfo> getGlobalSearchActivities() {
- return getSearchables().getGlobalSearchActivities();
+ return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities();
}
/**
* Gets the name of the global search activity.
*/
public ComponentName getGlobalSearchActivity() {
- return getSearchables().getGlobalSearchActivity();
+ return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity();
}
/**
* Gets the name of the web search activity.
*/
public ComponentName getWebSearchActivity() {
- return getSearchables().getWebSearchActivity();
+ return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity();
}
+ @Override
+ public ComponentName getAssistIntent(int userHandle) {
+ try {
+ if (userHandle != UserHandle.getCallingUserId()) {
+ // Requesting a different user, make sure that they have the permission
+ if (ActivityManager.checkComponentPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Binder.getCallingUid(), -1, true)
+ == PackageManager.PERMISSION_GRANTED) {
+ // Translate to the current user id, if caller wasn't aware
+ if (userHandle == UserHandle.USER_CURRENT) {
+ long identity = Binder.clearCallingIdentity();
+ userHandle = ActivityManagerNative.getDefault().getCurrentUser().id;
+ Binder.restoreCallingIdentity(identity);
+ }
+ } else {
+ String msg = "Permission Denial: "
+ + "Request to getAssistIntent for " + userHandle
+ + " but is calling from user " + UserHandle.getCallingUserId()
+ + "; this requires "
+ + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+ Slog.w(TAG, msg);
+ return null;
+ }
+ }
+ IPackageManager pm = AppGlobals.getPackageManager();
+ Intent assistIntent = new Intent(Intent.ACTION_ASSIST);
+ ResolveInfo info =
+ pm.resolveIntent(assistIntent,
+ assistIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.MATCH_DEFAULT_ONLY, userHandle);
+ if (info != null) {
+ return new ComponentName(
+ info.activityInfo.applicationInfo.packageName,
+ info.activityInfo.name);
+ }
+ } catch (RemoteException re) {
+ // Local call
+ Log.e(TAG, "RemoteException in getAssistIntent: " + re);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in getAssistIntent: " + e);
+ }
+ return null;
+ }
}
diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java
index f24d52f..30ca340 100644
--- a/core/java/android/server/search/Searchables.java
+++ b/core/java/android/server/search/Searchables.java
@@ -16,6 +16,7 @@
package android.server.search;
+import android.app.AppGlobals;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.ComponentName;
@@ -23,9 +24,12 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.Binder;
import android.os.Bundle;
+import android.os.RemoteException;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -38,6 +42,7 @@ import java.util.List;
/**
* This class maintains the information about all searchable activities.
+ * This is a hidden class.
*/
public class Searchables {
@@ -65,12 +70,19 @@ public class Searchables {
public static String ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME =
"com.google.android.providers.enhancedgooglesearch/.Launcher";
+ // Cache the package manager instance
+ final private IPackageManager mPm;
+ // User for which this Searchables caches information
+ private int mUserId;
+
/**
*
* @param context Context to use for looking up activities etc.
*/
- public Searchables (Context context) {
+ public Searchables (Context context, int userId) {
mContext = context;
+ mUserId = userId;
+ mPm = AppGlobals.getPackageManager();
}
/**
@@ -115,50 +127,50 @@ public class Searchables {
ActivityInfo ai = null;
try {
- ai = mContext.getPackageManager().
- getActivityInfo(activity, PackageManager.GET_META_DATA );
- String refActivityName = null;
+ ai = mPm.getActivityInfo(activity, PackageManager.GET_META_DATA, mUserId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error getting activity info " + re);
+ return null;
+ }
+ String refActivityName = null;
- // First look for activity-specific reference
- Bundle md = ai.metaData;
+ // First look for activity-specific reference
+ Bundle md = ai.metaData;
+ if (md != null) {
+ refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
+ }
+ // If not found, try for app-wide reference
+ if (refActivityName == null) {
+ md = ai.applicationInfo.metaData;
if (md != null) {
refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
}
- // If not found, try for app-wide reference
- if (refActivityName == null) {
- md = ai.applicationInfo.metaData;
- if (md != null) {
- refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
- }
- }
+ }
- // Irrespective of source, if a reference was found, follow it.
- if (refActivityName != null)
- {
- // This value is deprecated, return null
- if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
- return null;
- }
- String pkg = activity.getPackageName();
- ComponentName referredActivity;
- if (refActivityName.charAt(0) == '.') {
- referredActivity = new ComponentName(pkg, pkg + refActivityName);
- } else {
- referredActivity = new ComponentName(pkg, refActivityName);
- }
+ // Irrespective of source, if a reference was found, follow it.
+ if (refActivityName != null)
+ {
+ // This value is deprecated, return null
+ if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
+ return null;
+ }
+ String pkg = activity.getPackageName();
+ ComponentName referredActivity;
+ if (refActivityName.charAt(0) == '.') {
+ referredActivity = new ComponentName(pkg, pkg + refActivityName);
+ } else {
+ referredActivity = new ComponentName(pkg, refActivityName);
+ }
- // Now try the referred activity, and if found, cache
- // it against the original name so we can skip the check
- synchronized (this) {
- result = mSearchablesMap.get(referredActivity);
- if (result != null) {
- mSearchablesMap.put(activity, result);
- return result;
- }
+ // Now try the referred activity, and if found, cache
+ // it against the original name so we can skip the check
+ synchronized (this) {
+ result = mSearchablesMap.get(referredActivity);
+ if (result != null) {
+ mSearchablesMap.put(activity, result);
+ return result;
}
}
- } catch (PackageManager.NameNotFoundException e) {
- // case 3: no metadata
}
// Step 3. None found. Return null.
@@ -195,22 +207,22 @@ public class Searchables {
ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
= new ArrayList<SearchableInfo>();
- final PackageManager pm = mContext.getPackageManager();
-
// Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
List<ResolveInfo> searchList;
final Intent intent = new Intent(Intent.ACTION_SEARCH);
- searchList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
+
+ searchList = queryIntentActivities(intent, PackageManager.GET_META_DATA);
List<ResolveInfo> webSearchInfoList;
final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
- webSearchInfoList = pm.queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);
+ webSearchInfoList = queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);
// analyze each one, generate a Searchables record, and record
if (searchList != null || webSearchInfoList != null) {
int search_count = (searchList == null ? 0 : searchList.size());
int web_search_count = (webSearchInfoList == null ? 0 : webSearchInfoList.size());
int count = search_count + web_search_count;
+ long token = Binder.clearCallingIdentity();
for (int ii = 0; ii < count; ii++) {
// for each component, try to find metadata
ResolveInfo info = (ii < search_count)
@@ -229,6 +241,7 @@ public class Searchables {
}
}
}
+ Binder.restoreCallingIdentity(token);
}
List<ResolveInfo> newGlobalSearchActivities = findGlobalSearchActivities();
@@ -262,10 +275,8 @@ public class Searchables {
// Step 1 : Query the package manager for a list
// of activities that can handle the GLOBAL_SEARCH intent.
Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
- PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> activities =
- pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
-
+ queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (activities != null && !activities.isEmpty()) {
// Step 2: Rank matching activities according to our heuristics.
Collections.sort(activities, GLOBAL_SEARCH_RANKER);
@@ -301,10 +312,8 @@ public class Searchables {
Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
intent.setComponent(globalSearch);
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> activities =
- pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
-
+ List<ResolveInfo> activities = queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
if (activities != null && !activities.isEmpty()) {
return true;
}
@@ -374,9 +383,8 @@ public class Searchables {
}
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.setPackage(globalSearchActivity.getPackageName());
- PackageManager pm = mContext.getPackageManager();
List<ResolveInfo> activities =
- pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (activities != null && !activities.isEmpty()) {
ActivityInfo ai = activities.get(0).activityInfo;
@@ -387,6 +395,19 @@ public class Searchables {
return null;
}
+ private List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
+ List<ResolveInfo> activities = null;
+ try {
+ activities =
+ mPm.queryIntentActivities(intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ flags, mUserId);
+ } catch (RemoteException re) {
+ // Local call
+ }
+ return activities;
+ }
+
/**
* Returns the list of searchable activities.
*/
diff --git a/core/java/android/service/dreams/Dream.java b/core/java/android/service/dreams/Dream.java
index 83464c9..4e8b05b 100644
--- a/core/java/android/service/dreams/Dream.java
+++ b/core/java/android/service/dreams/Dream.java
@@ -1,392 +1,23 @@
/**
- *
+ * 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.service.dreams;
-import com.android.internal.policy.PolicyManager;
-
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Slog;
-import android.view.ActionMode;
-import android.view.IWindowManager;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.view.WindowManager.LayoutParams;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.WindowManager;
-import android.view.WindowManagerImpl;
-
/**
* @hide
- *
+ * Temporarily needed to not break existing apps.
*/
-public class Dream extends Service implements Window.Callback {
- private final static boolean DEBUG = true;
- private final static String TAG = "Dream";
-
- /**
- * The {@link Intent} that must be declared as handled by the service.
- * To be supported, the service must also require the
- * {@link android.Manifest.permission#BIND_WALLPAPER} permission so
- * that other applications can not abuse it.
- */
- @SdkConstant(SdkConstantType.SERVICE_ACTION)
- public static final String SERVICE_INTERFACE =
- "android.service.dreams.Dream";
-
- private Window mWindow;
-
- private WindowManager mWindowManager;
- private IDreamManager mSandman;
-
- private boolean mInteractive;
-
- final Handler mHandler = new Handler();
-
- boolean mFinished = false;
-
- // begin Window.Callback methods
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (!mInteractive) {
- finish();
- return true;
- }
- return mWindow.superDispatchKeyEvent(event);
- }
-
- @Override
- public boolean dispatchKeyShortcutEvent(KeyEvent event) {
- if (!mInteractive) {
- finish();
- return true;
- }
- return mWindow.superDispatchKeyShortcutEvent(event);
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (!mInteractive) {
- finish();
- return true;
- }
- return mWindow.superDispatchTouchEvent(event);
- }
-
- @Override
- public boolean dispatchTrackballEvent(MotionEvent event) {
- if (!mInteractive) {
- finish();
- return true;
- }
- return mWindow.superDispatchTrackballEvent(event);
- }
-
- @Override
- public boolean dispatchGenericMotionEvent(MotionEvent event) {
- if (!mInteractive) {
- finish();
- return true;
- }
- return mWindow.superDispatchGenericMotionEvent(event);
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- return false;
- }
-
- @Override
- public View onCreatePanelView(int featureId) {
- return null;
- }
-
- @Override
- public boolean onCreatePanelMenu(int featureId, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onPreparePanel(int featureId, View view, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onMenuOpened(int featureId, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onMenuItemSelected(int featureId, MenuItem item) {
- return false;
- }
-
- @Override
- public void onWindowAttributesChanged(LayoutParams attrs) {
-
- }
-
- @Override
- public void onContentChanged() {
-
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasFocus) {
-
- }
-
- @Override
- public void onAttachedToWindow() {
- mWindow.addFlags(
- WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- );
- lightsOut();
- }
-
- @Override
- public void onDetachedFromWindow() {
- }
-
- @Override
- public void onPanelClosed(int featureId, Menu menu) {
- }
-
- @Override
- public boolean onSearchRequested() {
- return false;
- }
-
- @Override
- public ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback callback) {
- return null;
- }
-
- @Override
- public void onActionModeStarted(ActionMode mode) {
- }
-
- @Override
- public void onActionModeFinished(ActionMode mode) {
- }
- // end Window.Callback methods
-
- public WindowManager getWindowManager() {
- return mWindowManager;
- }
-
- public Window getWindow() {
- return mWindow;
- }
-
- /**
- * Called when this Dream is constructed. Place your initialization here.
- *
- * Subclasses must call through to the superclass implementation.
- */
- @Override
- public void onCreate() {
- super.onCreate();
-
- if (DEBUG) Slog.v(TAG, "Dream created on thread " + Thread.currentThread().getId());
-
- mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams"));
- }
-
- /**
- * Called when this Dream is started. Place your initialization here.
- *
- * Subclasses must call through to the superclass implementation.
- *
- * XXX(dsandler) Might want to make this final and have a different method for clients to override
- */
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- return super.onStartCommand(intent, flags, startId);
- }
-
- /**
- * Inflate a layout resource and set it to be the content view for this Dream.
- * Behaves similarly to {@link android.app.Activity#setContentView(int)}.
- *
- * @param layoutResID Resource ID to be inflated.
- *
- * @see #setContentView(android.view.View)
- * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
- */
- public void setContentView(int layoutResID) {
- getWindow().setContentView(layoutResID);
- }
-
- /**
- * Set a view to be the content view for this Dream.
- * Behaves similarly to {@link android.app.Activity#setContentView(android.view.View)},
- * including using {@link ViewGroup.LayoutParams#MATCH_PARENT} as the layout height and width of the view.
- *
- * @param view The desired content to display.
- *
- * @see #setContentView(int)
- * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
- */
- public void setContentView(View view) {
- getWindow().setContentView(view);
- }
-
- /**
- * Set a view to be the content view for this Dream.
- * Behaves similarly to
- * {@link android.app.Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}.
- *
- * @param view The desired content to display.
- * @param params Layout parameters for the view.
- *
- * @see #setContentView(android.view.View)
- * @see #setContentView(int)
- */
- public void setContentView(View view, ViewGroup.LayoutParams params) {
- getWindow().setContentView(view, params);
- }
-
- /**
- * Add a view to the Dream's window, leaving other content views in place.
- *
- * @param view The desired content to display.
- * @param params Layout parameters for the view.
- */
- public void addContentView(View view, ViewGroup.LayoutParams params) {
- getWindow().addContentView(view, params);
- }
-
- /**
- * @param mInteractive the mInteractive to set
- */
- public void setInteractive(boolean mInteractive) {
- this.mInteractive = mInteractive;
- }
-
- /**
- * @return the mInteractive
- */
- public boolean isInteractive() {
- return mInteractive;
- }
-
- /** Convenience method for setting View.SYSTEM_UI_FLAG_LOW_PROFILE on the content view. */
- protected void lightsOut() {
- // turn the lights down low
- final View v = mWindow.getDecorView();
- if (v != null) {
- v.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
- }
- }
-
- /**
- * Finds a view that was identified by the id attribute from the XML that
- * was processed in {@link #onCreate}.
- *
- * @return The view if found or null otherwise.
- */
- public View findViewById(int id) {
- return getWindow().findViewById(id);
- }
-
- /**
- * Called when this Dream is being removed from the screen and stopped.
- */
- @Override
- public void onDestroy() {
- super.onDestroy();
- mWindowManager.removeView(mWindow.getDecorView());
- }
-
- /**
- * Creates a new dream window, attaches the current content view, and shows it.
- *
- * @param windowToken Binder to attach to the window to allow access to the correct window type.
- * @hide
- */
- final /*package*/ void attach(IBinder windowToken) {
- if (DEBUG) Slog.v(TAG, "Dream attached on thread " + Thread.currentThread().getId());
-
- mWindow = PolicyManager.makeNewWindow(this);
- mWindow.setCallback(this);
- mWindow.requestFeature(Window.FEATURE_NO_TITLE);
- mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));
-
- if (DEBUG) Slog.v(TAG, "attaching window token: " + windowToken
- + " to window of type " + WindowManager.LayoutParams.TYPE_DREAM);
-
- WindowManager.LayoutParams lp = mWindow.getAttributes();
- lp.type = WindowManager.LayoutParams.TYPE_DREAM;
- lp.token = windowToken;
- lp.windowAnimations = com.android.internal.R.style.Animation_Dream;
-
- //WindowManagerImpl.getDefault().addView(mWindow.getDecorView(), lp);
-
- if (DEBUG) Slog.v(TAG, "created and attached window: " + mWindow);
-
- mWindow.setWindowManager(null, windowToken, "dream", true);
- mWindowManager = mWindow.getWindowManager();
-
- // now make it visible
- mHandler.post(new Runnable(){
- @Override
- public void run() {
- if (DEBUG) Slog.v(TAG, "Dream window added on thread " + Thread.currentThread().getId());
-
- getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes());
- }});
- }
-
- /**
- * Stop the dream and wake up.
- *
- * After this method is called, the service will be stopped.
- */
- public void finish() {
- if (mFinished) return;
- try {
- mSandman.awaken(); // assuming we were started by the DreamManager
- stopSelf(); // if launched via any other means
- mFinished = true;
- } catch (RemoteException ex) {
- // sigh
- }
- }
-
- class IDreamServiceWrapper extends IDreamService.Stub {
- public IDreamServiceWrapper() {
- }
-
- public void attach(IBinder windowToken) {
- Dream.this.attach(windowToken);
- }
- }
-
- /**
- * Implement to return the implementation of the internal accessibility
- * service interface. Subclasses should not override.
- */
- @Override
- public final IBinder onBind(Intent intent) {
- return new IDreamServiceWrapper();
- }
+public class Dream extends DreamService {
}
diff --git a/core/java/android/service/dreams/DreamManagerService.java b/core/java/android/service/dreams/DreamManagerService.java
deleted file mode 100644
index 4a14ced..0000000
--- a/core/java/android/service/dreams/DreamManagerService.java
+++ /dev/null
@@ -1,184 +0,0 @@
-package android.service.dreams;
-
-import static android.provider.Settings.Secure.SCREENSAVER_COMPONENT;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-import com.android.internal.view.IInputMethod;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.util.Log;
-import android.util.Slog;
-import android.view.IWindowManager;
-import android.view.WindowManager;
-
-/**
- *
- * @hide
- *
- */
-
-public class DreamManagerService
- extends IDreamManager.Stub
- implements ServiceConnection
-{
- private static final boolean DEBUG = true;
- private static final String TAG = "DreamManagerService";
-
- final Object mLock = new Object[0];
-
- private Context mContext;
- private IWindowManager mIWindowManager;
-
- private ComponentName mCurrentDreamComponent;
- private IDreamService mCurrentDream;
- private Binder mCurrentDreamToken;
-
- public DreamManagerService(Context context) {
- if (DEBUG) Slog.v(TAG, "DreamManagerService startup");
- mContext = context;
- mIWindowManager = IWindowManager.Stub.asInterface(
- ServiceManager.getService(Context.WINDOW_SERVICE));
- }
-
- private void checkPermission(String permission) {
- if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(permission)) {
- throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
- + ", must have permission " + permission);
- }
- }
-
- // IDreamManager method
- public void dream() {
- ComponentName name = getDreamComponent();
- if (name != null) {
- synchronized (mLock) {
- final long ident = Binder.clearCallingIdentity();
- try {
- bindDreamComponentL(name, false);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
- }
-
- // IDreamManager method
- public void setDreamComponent(ComponentName name) {
- Settings.Secure.putString(mContext.getContentResolver(), SCREENSAVER_COMPONENT, name.flattenToString());
- }
-
- // IDreamManager method
- public ComponentName getDreamComponent() {
- // TODO(dsandler) don't load this every time, watch the value
- String component = Settings.Secure.getString(mContext.getContentResolver(), SCREENSAVER_COMPONENT);
- if (component == null) {
- component = mContext.getResources().getString(
- com.android.internal.R.string.config_defaultDreamComponent);
- }
- if (component != null) {
- return ComponentName.unflattenFromString(component);
- } else {
- return null;
- }
- }
-
- // IDreamManager method
- public void testDream(ComponentName name) {
- if (DEBUG) Slog.v(TAG, "startDream name=" + name
- + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
-// checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
- synchronized (mLock) {
- final long ident = Binder.clearCallingIdentity();
- try {
- bindDreamComponentL(name, true);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
- // IDreamManager method
- public void awaken() {
- if (DEBUG) Slog.v(TAG, "awaken()");
- synchronized (mLock) {
- if (mCurrentDream != null) {
- mContext.unbindService(this);
- }
- }
- }
-
- public void bindDreamComponentL(ComponentName componentName, boolean test) {
- if (DEBUG) Slog.v(TAG, "bindDreamComponent: componentName=" + componentName
- + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
-
- Intent intent = new Intent(Intent.ACTION_MAIN)
- .setComponent(componentName)
- .addFlags(
- Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
- )
- .putExtra("android.dreams.TEST", test);
-
- if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
- Slog.w(TAG, "unable to bind service: " + componentName);
- return;
- }
- mCurrentDreamComponent = componentName;
- mCurrentDreamToken = new Binder();
- try {
- if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurrentDreamToken
- + " for window type: " + WindowManager.LayoutParams.TYPE_DREAM);
- mIWindowManager.addWindowToken(mCurrentDreamToken,
- WindowManager.LayoutParams.TYPE_DREAM);
- } catch (RemoteException e) {
- Slog.w(TAG, "Unable to add window token. Proceed at your own risk.");
- }
-
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- if (DEBUG) Slog.v(TAG, "connected to dream: " + name + " binder=" + service + " thread=" + Thread.currentThread().getId());
-
- mCurrentDream = IDreamService.Stub.asInterface(service);
- try {
- if (DEBUG) Slog.v(TAG, "attaching with token:" + mCurrentDreamToken);
- mCurrentDream.attach(mCurrentDreamToken);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Unable to send window token to dream:" + ex);
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- if (DEBUG) Slog.v(TAG, "disconnected: " + name + " service: " + mCurrentDream);
- mCurrentDream = null;
- mCurrentDreamToken = null;
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
-
- pw.println("Dreamland:");
- pw.print(" component="); pw.println(mCurrentDreamComponent);
- pw.print(" token="); pw.println(mCurrentDreamToken);
- pw.print(" dream="); pw.println(mCurrentDream);
- }
-
- public void systemReady() {
- if (DEBUG) Slog.v(TAG, "ready to dream!");
- }
-
-}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
new file mode 100644
index 0000000..03b685b
--- /dev/null
+++ b/core/java/android/service/dreams/DreamService.java
@@ -0,0 +1,625 @@
+/**
+ * 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.service.dreams;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.Slog;
+import android.view.ActionMode;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.internal.policy.PolicyManager;
+
+/**
+ * Extend this class to implement a custom Dream.
+ *
+ * <p>Dreams are interactive screensavers launched when a charging device is idle, or docked in a
+ * desk dock. Dreams provide another modality for apps to express themselves, tailored for
+ * an exhibition/lean-back experience.</p>
+ *
+ * <p>Dreams should be declared in the manifest as follows:</p>
+ * <pre>
+ * &lt;service
+ * android:name=".MyDream"
+ * android:exported="true"
+ * android:icon="@drawable/my_icon"
+ * android:label="@string/my_dream_label" >
+ *
+ * &lt;intent-filter>
+ * &lt;action android:name="android.service.dreams.DreamService" />
+ * &lt;category android:name="android.intent.category.DEFAULT" />
+ * &lt;/intent-filter>
+ *
+ * &lt;!-- Point to additional information for this dream (optional) -->
+ * &lt;meta-data
+ * android:name="android.service.dream"
+ * android:resource="@xml/my_dream" />
+ * &lt;/service>
+ * </pre>
+ * <p>If specified, additional information for the dream is defined using the
+ * <code>&lt;{@link android.R.styleable#Dream dream}&gt;</code> element. For example:</p>
+ * <pre>
+ * (in res/xml/my_dream.xml)
+ *
+ * &lt;dream xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:settingsActivity="com.example.app/.MyDreamSettingsActivity" />
+ * </pre>
+ */
+public class DreamService extends Service implements Window.Callback {
+ private final static boolean DEBUG = true;
+ private final String TAG = DreamService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]";
+
+ /**
+ * The name of the dream manager service.
+ * @hide
+ */
+ public static final String DREAM_SERVICE = "dreams";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.dreams.DreamService";
+
+ /**
+ * Name under which a Dream publishes information about itself.
+ * This meta-data must reference an XML resource containing
+ * a <code>&lt;{@link android.R.styleable#Dream dream}&gt;</code>
+ * tag.
+ */
+ public static final String DREAM_META_DATA = "android.service.dream";
+
+ private final Handler mHandler = new Handler();
+ private IBinder mWindowToken;
+ private Window mWindow;
+ private WindowManager mWindowManager;
+ private IDreamManager mSandman;
+ private boolean mInteractive = false;
+ private boolean mLowProfile = true;
+ private boolean mFullscreen = false;
+ private boolean mScreenBright = false;
+ private boolean mFinished;
+
+ // begin Window.Callback methods
+ /** {@inheritDoc} */
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ // TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK
+ if (!mInteractive) {
+ if (DEBUG) Slog.v(TAG, "Finishing on keyEvent");
+ safelyFinish();
+ return true;
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ if (DEBUG) Slog.v(TAG, "Finishing on back key");
+ safelyFinish();
+ return true;
+ }
+ return mWindow.superDispatchKeyEvent(event);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ if (!mInteractive) {
+ if (DEBUG) Slog.v(TAG, "Finishing on keyShortcutEvent");
+ safelyFinish();
+ return true;
+ }
+ return mWindow.superDispatchKeyShortcutEvent(event);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ // TODO: create more flexible version of mInteractive that allows clicks
+ // but finish()es on any other kind of activity
+ if (!mInteractive) {
+ if (DEBUG) Slog.v(TAG, "Finishing on touchEvent");
+ safelyFinish();
+ return true;
+ }
+ return mWindow.superDispatchTouchEvent(event);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ if (!mInteractive) {
+ if (DEBUG) Slog.v(TAG, "Finishing on trackballEvent");
+ safelyFinish();
+ return true;
+ }
+ return mWindow.superDispatchTrackballEvent(event);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ if (!mInteractive) {
+ if (DEBUG) Slog.v(TAG, "Finishing on genericMotionEvent");
+ safelyFinish();
+ return true;
+ }
+ return mWindow.superDispatchGenericMotionEvent(event);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public View onCreatePanelView(int featureId) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean onCreatePanelMenu(int featureId, Menu menu) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean onPreparePanel(int featureId, View view, Menu menu) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean onMenuOpened(int featureId, Menu menu) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onWindowAttributesChanged(LayoutParams attrs) {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onContentChanged() {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onAttachedToWindow() {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onDetachedFromWindow() {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onPanelClosed(int featureId, Menu menu) {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean onSearchRequested() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback callback) {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onActionModeFinished(ActionMode mode) {
+ }
+ // end Window.Callback methods
+
+ // begin public api
+ /**
+ * Retrieves the current {@link android.view.WindowManager} for the dream.
+ * Behaves similarly to {@link android.app.Activity#getWindowManager()}.
+ *
+ * @return The current window manager, or null if the dream is not started.
+ */
+ public WindowManager getWindowManager() {
+ return mWindowManager;
+ }
+
+ /**
+ * Retrieves the current {@link android.view.Window} for the dream.
+ * Behaves similarly to {@link android.app.Activity#getWindow()}.
+ *
+ * @return The current window, or null if the dream is not started.
+ */
+ public Window getWindow() {
+ return mWindow;
+ }
+
+ /**
+ * Inflates a layout resource and set it to be the content view for this Dream.
+ * Behaves similarly to {@link android.app.Activity#setContentView(int)}.
+ *
+ * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
+ *
+ * @param layoutResID Resource ID to be inflated.
+ *
+ * @see #setContentView(android.view.View)
+ * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
+ */
+ public void setContentView(int layoutResID) {
+ getWindow().setContentView(layoutResID);
+ }
+
+ /**
+ * Sets a view to be the content view for this Dream.
+ * Behaves similarly to {@link android.app.Activity#setContentView(android.view.View)},
+ * including using {@link ViewGroup.LayoutParams#MATCH_PARENT} as the layout height and width of the view.
+ *
+ * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
+ * @param view The desired content to display.
+ *
+ * @see #setContentView(int)
+ * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
+ */
+ public void setContentView(View view) {
+ getWindow().setContentView(view);
+ }
+
+ /**
+ * Sets a view to be the content view for this Dream.
+ * Behaves similarly to
+ * {@link android.app.Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}.
+ *
+ * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
+ *
+ * @param view The desired content to display.
+ * @param params Layout parameters for the view.
+ *
+ * @see #setContentView(android.view.View)
+ * @see #setContentView(int)
+ */
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ getWindow().setContentView(view, params);
+ }
+
+ /**
+ * Adds a view to the Dream's window, leaving other content views in place.
+ *
+ * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
+ *
+ * @param view The desired content to display.
+ * @param params Layout parameters for the view.
+ */
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ getWindow().addContentView(view, params);
+ }
+
+ /**
+ * Finds a view that was identified by the id attribute from the XML that
+ * was processed in {@link #onCreate}.
+ *
+ * <p>Note: Requires a window, do not call before {@link #onAttachedToWindow()}</p>
+ *
+ * @return The view if found or null otherwise.
+ */
+ public View findViewById(int id) {
+ return getWindow().findViewById(id);
+ }
+
+ /**
+ * Marks this dream as interactive to receive input events.
+ *
+ * <p>Non-interactive dreams (default) will dismiss on the first input event.</p>
+ *
+ * <p>Interactive dreams should call {@link #finish()} to dismiss themselves.</p>
+ *
+ * @param interactive True if this dream will handle input events.
+ */
+ public void setInteractive(boolean interactive) {
+ mInteractive = interactive;
+ }
+
+ /**
+ * Returns whether or not this dream is interactive. Defaults to false.
+ *
+ * @see #setInteractive(boolean)
+ */
+ public boolean isInteractive() {
+ return mInteractive;
+ }
+
+ /**
+ * Sets View.SYSTEM_UI_FLAG_LOW_PROFILE on the content view.
+ *
+ * @param lowProfile True to set View.SYSTEM_UI_FLAG_LOW_PROFILE
+ */
+ public void setLowProfile(boolean lowProfile) {
+ mLowProfile = lowProfile;
+ int flag = View.SYSTEM_UI_FLAG_LOW_PROFILE;
+ applySystemUiVisibilityFlags(mLowProfile ? flag : 0, flag);
+ }
+
+ /**
+ * Returns whether or not this dream is in low profile mode. Defaults to true.
+ *
+ * @see #setLowProfile(boolean)
+ */
+ public boolean isLowProfile() {
+ return getSystemUiVisibilityFlagValue(View.SYSTEM_UI_FLAG_LOW_PROFILE, mLowProfile);
+ }
+
+ /**
+ * Sets View.SYSTEM_UI_FLAG_FULLSCREEN on the content view.
+ *
+ * @param fullscreen True to set View.SYSTEM_UI_FLAG_FULLSCREEN
+ */
+ public void setFullscreen(boolean fullscreen) {
+ mFullscreen = fullscreen;
+ int flag = View.SYSTEM_UI_FLAG_FULLSCREEN;
+ applySystemUiVisibilityFlags(mFullscreen ? flag : 0, flag);
+ }
+
+ /**
+ * Returns whether or not this dream is in fullscreen mode. Defaults to false.
+ *
+ * @see #setFullscreen(boolean)
+ */
+ public boolean isFullscreen() {
+ return getSystemUiVisibilityFlagValue(View.SYSTEM_UI_FLAG_FULLSCREEN, mFullscreen);
+ }
+
+ /**
+ * Marks this dream as keeping the screen bright while dreaming.
+ *
+ * @param screenBright True to keep the screen bright while dreaming.
+ */
+ public void setScreenBright(boolean screenBright) {
+ mScreenBright = screenBright;
+ int flag = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+ applyWindowFlags(mScreenBright ? flag : 0, flag);
+ }
+
+ /**
+ * Returns whether or not this dream keeps the screen bright while dreaming. Defaults to false,
+ * allowing the screen to dim if necessary.
+ *
+ * @see #setScreenBright(boolean)
+ */
+ public boolean isScreenBright() {
+ return getWindowFlagValue(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, mScreenBright);
+ }
+
+ /**
+ * Called when this Dream is constructed. Place your initialization here.
+ *
+ * <p>Subclasses must call through to the superclass implementation.</p>
+ */
+ @Override
+ public void onCreate() {
+ if (DEBUG) Slog.v(TAG, "onCreate() on thread " + Thread.currentThread().getId());
+ super.onCreate();
+ loadSandman();
+ }
+
+ /**
+ * Called when this Dream is started. The window is created and visible at this point.
+ */
+ public void onStart() {
+ if (DEBUG) Slog.v(TAG, "onStart()");
+ // hook for subclasses
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final IBinder onBind(Intent intent) {
+ if (DEBUG) Slog.v(TAG, "onBind() intent = " + intent);
+ return new DreamServiceWrapper();
+ }
+
+ /**
+ * Stops the dream, detaches from the window, and wakes up.
+ *
+ * <p>Subclasses must call through to the superclass implementation.</p>
+ *
+ * <p>After this method is called, the service will be stopped.</p>
+ */
+ public void finish() {
+ if (DEBUG) Slog.v(TAG, "finish()");
+ finishInternal();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void onDestroy() {
+ if (DEBUG) Slog.v(TAG, "onDestroy()");
+ super.onDestroy();
+
+ if (DEBUG) Slog.v(TAG, "Removing window");
+ try {
+ mWindowManager.removeView(mWindow.getDecorView());
+ } catch (Throwable t) {
+ Slog.w(TAG, "Crashed removing window view", t);
+ }
+ }
+ // end public api
+
+ private void loadSandman() {
+ mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE));
+ }
+
+ private final void attach(IBinder windowToken) {
+ if (DEBUG) Slog.v(TAG, "Attached on thread " + Thread.currentThread().getId());
+
+ if (mSandman == null) {
+ Slog.w(TAG, "No dream manager found, super.onCreate may not have been called");
+ loadSandman();
+ }
+ mWindowToken = windowToken;
+ mWindow = PolicyManager.makeNewWindow(this);
+ mWindow.setCallback(this);
+ mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+ mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));
+
+ if (DEBUG) Slog.v(TAG, String.format("Attaching window token: %s to window of type %s",
+ windowToken, WindowManager.LayoutParams.TYPE_DREAM));
+
+ WindowManager.LayoutParams lp = mWindow.getAttributes();
+ lp.type = WindowManager.LayoutParams.TYPE_DREAM;
+ lp.token = windowToken;
+ lp.windowAnimations = com.android.internal.R.style.Animation_Dream;
+ lp.flags |= ( WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+ | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
+ | (mScreenBright ? WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON : 0)
+ );
+ mWindow.setAttributes(lp);
+
+ if (DEBUG) Slog.v(TAG, "Created and attached window: " + mWindow);
+
+ mWindow.setWindowManager(null, windowToken, "dream", true);
+ mWindowManager = mWindow.getWindowManager();
+
+ // now make it visible (on the ui thread)
+ mHandler.post(new Runnable(){
+ @Override
+ public void run() {
+ if (DEBUG) Slog.v(TAG, "Window added on thread " + Thread.currentThread().getId());
+ try {
+ applySystemUiVisibilityFlags(
+ (mLowProfile ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0)
+ | (mFullscreen ? View.SYSTEM_UI_FLAG_FULLSCREEN : 0),
+ View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN);
+ getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes());
+ } catch (Throwable t) {
+ Slog.w("Crashed adding window view", t);
+ safelyFinish();
+ return;
+ }
+
+ // start it up
+ try {
+ onStart();
+ } catch (Throwable t) {
+ Slog.w("Crashed in onStart()", t);
+ safelyFinish();
+ }
+ }});
+ }
+
+ private void safelyFinish() {
+ if (DEBUG) Slog.v(TAG, "safelyFinish()");
+ try {
+ finish();
+ } catch (Throwable t) {
+ Slog.w(TAG, "Crashed in safelyFinish()", t);
+ finishInternal();
+ return;
+ }
+
+ if (!mFinished) {
+ Slog.w(TAG, "Bad dream, did not call super.finish()");
+ finishInternal();
+ }
+ }
+
+ private void finishInternal() {
+ if (DEBUG) Slog.v(TAG, "finishInternal() mFinished = " + mFinished);
+ if (mFinished) return;
+ try {
+ mFinished = true;
+
+ if (mSandman != null) {
+ mSandman.finishSelf(mWindowToken);
+ } else {
+ Slog.w(TAG, "No dream manager found");
+ }
+ stopSelf(); // if launched via any other means
+
+ } catch (Throwable t) {
+ Slog.w(TAG, "Crashed in finishInternal()", t);
+ }
+ }
+
+ private boolean getWindowFlagValue(int flag, boolean defaultValue) {
+ return mWindow == null ? defaultValue : (mWindow.getAttributes().flags & flag) != 0;
+ }
+
+ private void applyWindowFlags(int flags, int mask) {
+ if (mWindow != null) {
+ WindowManager.LayoutParams lp = mWindow.getAttributes();
+ lp.flags = applyFlags(lp.flags, flags, mask);
+ mWindow.setAttributes(lp);
+ mWindowManager.updateViewLayout(mWindow.getDecorView(), lp);
+ }
+ }
+
+ private boolean getSystemUiVisibilityFlagValue(int flag, boolean defaultValue) {
+ View v = mWindow == null ? null : mWindow.getDecorView();
+ return v == null ? defaultValue : (v.getSystemUiVisibility() & flag) != 0;
+ }
+
+ private void applySystemUiVisibilityFlags(int flags, int mask) {
+ View v = mWindow == null ? null : mWindow.getDecorView();
+ if (v != null) {
+ v.setSystemUiVisibility(applyFlags(v.getSystemUiVisibility(), flags, mask));
+ }
+ }
+
+ private int applyFlags(int oldFlags, int flags, int mask) {
+ return (oldFlags&~mask) | (flags&mask);
+ }
+
+ private class DreamServiceWrapper extends IDreamService.Stub {
+ public void attach(IBinder windowToken) {
+ DreamService.this.attach(windowToken);
+ }
+ }
+
+}
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
index 7225013..1c1b390 100644
--- a/core/java/android/service/dreams/IDreamManager.aidl
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -19,12 +19,16 @@ package android.service.dreams;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.content.ComponentName;
+import android.os.IBinder;
/** @hide */
interface IDreamManager {
void dream();
void awaken();
- void setDreamComponent(in ComponentName componentName);
- ComponentName getDreamComponent();
+ void setDreamComponents(in ComponentName[] componentNames);
+ ComponentName[] getDreamComponents();
+ ComponentName getDefaultDreamComponent();
void testDream(in ComponentName componentName);
+ boolean isDreaming();
+ void finishSelf(in IBinder token);
} \ No newline at end of file
diff --git a/core/java/android/service/wallpaper/IWallpaperConnection.aidl b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
index b09ccab..f9c5aaa 100644
--- a/core/java/android/service/wallpaper/IWallpaperConnection.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperConnection.aidl
@@ -24,5 +24,6 @@ import android.service.wallpaper.IWallpaperEngine;
*/
interface IWallpaperConnection {
void attachEngine(IWallpaperEngine engine);
+ void engineShown(IWallpaperEngine engine);
ParcelFileDescriptor setWallpaper(String name);
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 3e0942c..86bbc55 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -36,10 +36,10 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
-import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import android.util.LogPrinter;
+import android.view.Display;
import android.view.Gravity;
import android.view.IWindowSession;
import android.view.InputChannel;
@@ -50,9 +50,8 @@ import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewRootImpl;
import android.view.WindowManager;
-import android.view.WindowManagerImpl;
+import android.view.WindowManagerGlobal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -98,6 +97,7 @@ public abstract class WallpaperService extends Service {
private static final int MSG_WALLPAPER_OFFSETS = 10020;
private static final int MSG_WALLPAPER_COMMAND = 10025;
private static final int MSG_WINDOW_RESIZED = 10030;
+ private static final int MSG_WINDOW_MOVED = 10035;
private static final int MSG_TOUCH_EVENT = 10040;
private Looper mCallbackLooper;
@@ -253,13 +253,19 @@ public abstract class WallpaperService extends Service {
final BaseIWindow mWindow = new BaseIWindow() {
@Override
- public void resized(int w, int h, Rect contentInsets,
+ public void resized(Rect frame, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
reportDraw ? 1 : 0);
mCaller.sendMessage(msg);
}
-
+
+ @Override
+ public void moved(int newX, int newY) {
+ Message msg = mCaller.obtainMessageII(MSG_WINDOW_MOVED, newX, newY);
+ mCaller.sendMessage(msg);
+ }
+
@Override
public void dispatchAppVisibility(boolean visible) {
// We don't do this in preview mode; we'll let the preview
@@ -290,7 +296,8 @@ public abstract class WallpaperService extends Service {
}
}
}
-
+
+ @Override
public void dispatchWallpaperCommand(String action, int x, int y,
int z, Bundle extras, boolean sync) {
synchronized (mLock) {
@@ -599,13 +606,13 @@ public abstract class WallpaperService extends Service {
if (!mCreated) {
mLayout.type = mIWallpaperEngine.mWindowType;
- mLayout.gravity = Gravity.LEFT|Gravity.TOP;
+ mLayout.gravity = Gravity.START|Gravity.TOP;
mLayout.setTitle(WallpaperService.this.getClass().getName());
mLayout.windowAnimations =
com.android.internal.R.style.Animation_Wallpaper;
mInputChannel = new InputChannel();
- if (mSession.add(mWindow, mWindow.mSeq, mLayout, View.VISIBLE, mContentInsets,
- mInputChannel) < 0) {
+ if (mSession.addToDisplay(mWindow, mWindow.mSeq, mLayout, View.VISIBLE,
+ Display.DEFAULT_DISPLAY, mContentInsets, mInputChannel) < 0) {
Log.w(TAG, "Failed to add window while updating wallpaper surface.");
return;
}
@@ -665,8 +672,8 @@ public abstract class WallpaperService extends Service {
}
}
- redrawNeeded |= creating
- || (relayoutResult&WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0;
+ redrawNeeded |= creating || (relayoutResult
+ & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0;
if (forceReport || creating || surfaceCreating
|| formatChanged || sizeChanged) {
@@ -753,7 +760,7 @@ public abstract class WallpaperService extends Service {
mWindowToken = wrapper.mWindowToken;
mSurfaceHolder.setSizeFromLayout();
mInitializing = true;
- mSession = ViewRootImpl.getWindowSession(getMainLooper());
+ mSession = WindowManagerGlobal.getWindowSession(getMainLooper());
mWindow.setSession(mSession);
@@ -1013,6 +1020,12 @@ public abstract class WallpaperService extends Service {
mEngine = engine;
mActiveEngines.add(engine);
engine.attach(this);
+ try {
+ mConnection.engineShown(this);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Wallpaper host disappeared", e);
+ return;
+ }
return;
}
case DO_DETACH: {
@@ -1044,6 +1057,9 @@ public abstract class WallpaperService extends Service {
mEngine.updateSurface(true, false, reportDraw);
mEngine.doOffsetsChanged(true);
} break;
+ case MSG_WINDOW_MOVED: {
+ // Do nothing. What does it mean for a Wallpaper to move?
+ } break;
case MSG_TOUCH_EVENT: {
boolean skip = false;
MotionEvent ev = (MotionEvent)message.obj;
diff --git a/core/java/android/speech/tts/BlockingAudioTrack.java b/core/java/android/speech/tts/BlockingAudioTrack.java
index fcadad7..186cb49 100644
--- a/core/java/android/speech/tts/BlockingAudioTrack.java
+++ b/core/java/android/speech/tts/BlockingAudioTrack.java
@@ -67,12 +67,11 @@ class BlockingAudioTrack {
private int mAudioBufferSize;
private int mBytesWritten = 0;
+ // Need to be seen by stop() which can be called from another thread. mAudioTrack will be
+ // set to null only after waitAndRelease().
+ private Object mAudioTrackLock = new Object();
private AudioTrack mAudioTrack;
private volatile boolean mStopped;
- // Locks the initialization / uninitialization of the audio track.
- // This is required because stop() will throw an illegal state exception
- // if called before init() or after mAudioTrack.release().
- private final Object mAudioTrackLock = new Object();
BlockingAudioTrack(int streamType, int sampleRate,
int audioFormat, int channelCount,
@@ -93,12 +92,17 @@ class BlockingAudioTrack {
mStopped = false;
}
- public void init() {
+ public boolean init() {
AudioTrack track = createStreamingAudioTrack();
-
synchronized (mAudioTrackLock) {
mAudioTrack = track;
}
+
+ if (track == null) {
+ return false;
+ } else {
+ return true;
+ }
}
public void stop() {
@@ -106,20 +110,35 @@ class BlockingAudioTrack {
if (mAudioTrack != null) {
mAudioTrack.stop();
}
+ mStopped = true;
}
- mStopped = true;
}
public int write(byte[] data) {
- if (mAudioTrack == null || mStopped) {
+ AudioTrack track = null;
+ synchronized (mAudioTrackLock) {
+ track = mAudioTrack;
+ }
+
+ if (track == null || mStopped) {
return -1;
}
- final int bytesWritten = writeToAudioTrack(mAudioTrack, data);
+ final int bytesWritten = writeToAudioTrack(track, data);
+
mBytesWritten += bytesWritten;
return bytesWritten;
}
public void waitAndRelease() {
+ AudioTrack track = null;
+ synchronized (mAudioTrackLock) {
+ track = mAudioTrack;
+ }
+ if (track == null) {
+ if (DBG) Log.d(TAG, "Audio track null [duplicate call to waitAndRelease ?]");
+ return;
+ }
+
// For "small" audio tracks, we have to stop() them to make them mixable,
// else the audio subsystem will wait indefinitely for us to fill the buffer
// before rendering the track mixable.
@@ -129,11 +148,11 @@ class BlockingAudioTrack {
if (mBytesWritten < mAudioBufferSize && !mStopped) {
if (DBG) {
Log.d(TAG, "Stopping audio track to flush audio, state was : " +
- mAudioTrack.getPlayState() + ",stopped= " + mStopped);
+ track.getPlayState() + ",stopped= " + mStopped);
}
mIsShortUtterance = true;
- mAudioTrack.stop();
+ track.stop();
}
// Block until the audio track is done only if we haven't stopped yet.
@@ -145,11 +164,11 @@ class BlockingAudioTrack {
// The last call to AudioTrack.write( ) will return only after
// all data from the audioTrack has been sent to the mixer, so
// it's safe to release at this point.
- if (DBG) Log.d(TAG, "Releasing audio track [" + mAudioTrack.hashCode() + "]");
- synchronized (mAudioTrackLock) {
- mAudioTrack.release();
+ if (DBG) Log.d(TAG, "Releasing audio track [" + track.hashCode() + "]");
+ synchronized(mAudioTrackLock) {
mAudioTrack = null;
}
+ track.release();
}
diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java
index 04c3377..3e33e8e 100644
--- a/core/java/android/speech/tts/FileSynthesisCallback.java
+++ b/core/java/android/speech/tts/FileSynthesisCallback.java
@@ -95,6 +95,22 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
}
}
+ /**
+ * Checks whether a given file exists, and deletes it if it does.
+ */
+ private boolean maybeCleanupExistingFile(File file) {
+ if (file.exists()) {
+ Log.v(TAG, "File " + file + " exists, deleting.");
+ if (!file.delete()) {
+ Log.e(TAG, "Failed to delete " + file);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
@Override
public int getMaxBufferSize() {
return MAX_AUDIO_BUFFER_SIZE;
@@ -120,6 +136,11 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
cleanUp();
throw new IllegalArgumentException("FileSynthesisRequest.start() called twice");
}
+
+ if (!maybeCleanupExistingFile(mFileName)) {
+ return TextToSpeech.ERROR;
+ }
+
mSampleRateInHz = sampleRateInHz;
mAudioFormat = audioFormat;
mChannelCount = channelCount;
@@ -166,6 +187,12 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
public int done() {
if (DBG) Log.d(TAG, "FileSynthesisRequest.done()");
synchronized (mStateLock) {
+ if (mDone) {
+ if (DBG) Log.d(TAG, "Duplicate call to done()");
+ // This preserves existing behaviour. Earlier, if done was called twice
+ // we'd return ERROR because mFile == null and we'd add to logspam.
+ return TextToSpeech.ERROR;
+ }
if (mStopped) {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
diff --git a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
index d299d70..e853c9e 100644
--- a/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
+++ b/core/java/android/speech/tts/SynthesisPlaybackQueueItem.java
@@ -87,7 +87,10 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
dispatcher.dispatchOnStart();
- mAudioTrack.init();
+ if (!mAudioTrack.init()) {
+ dispatcher.dispatchOnError();
+ return;
+ }
try {
byte[] buffer = null;
@@ -242,4 +245,3 @@ final class SynthesisPlaybackQueueItem extends PlaybackQueueItem {
}
}
}
-
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 7a174af..5e367cb 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -1282,6 +1282,7 @@ public class TextToSpeech {
}
};
+ @Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "Connected to " + name);
synchronized(mStartLock) {
@@ -1305,6 +1306,7 @@ public class TextToSpeech {
return mCallback;
}
+ @Override
public void onServiceDisconnected(ComponentName name) {
synchronized(mStartLock) {
mService = null;
@@ -1317,24 +1319,33 @@ public class TextToSpeech {
public void disconnect() {
mContext.unbindService(this);
+
+ synchronized (mStartLock) {
+ mService = null;
+ // If this is the active connection, clear it
+ if (mServiceConnection == this) {
+ mServiceConnection = null;
+ }
+
+ }
}
public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
- try {
- synchronized (mStartLock) {
+ synchronized (mStartLock) {
+ try {
if (mService == null) {
Log.w(TAG, method + " failed: not connected to TTS engine");
return errorResult;
}
return action.run(mService);
+ } catch (RemoteException ex) {
+ Log.e(TAG, method + " failed", ex);
+ if (reconnect) {
+ disconnect();
+ initTts();
+ }
+ return errorResult;
}
- } catch (RemoteException ex) {
- Log.e(TAG, method + " failed", ex);
- if (reconnect) {
- disconnect();
- initTts();
- }
- return errorResult;
}
}
}
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 4c1a0af..d124e68 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -549,7 +549,7 @@ public abstract class TextToSpeechService extends Service {
@Override
public boolean isValid() {
if (mText == null) {
- Log.wtf(TAG, "Got null text");
+ Log.e(TAG, "null synthesis text");
return false;
}
if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH) {
@@ -641,14 +641,6 @@ public abstract class TextToSpeechService extends Service {
}
@Override
- public boolean isValid() {
- if (!super.isValid()) {
- return false;
- }
- return checkFile(mFile);
- }
-
- @Override
protected AbstractSynthesisCallback createSynthesisCallback() {
return new FileSynthesisCallback(mFile);
}
@@ -664,33 +656,6 @@ public abstract class TextToSpeechService extends Service {
}
return status;
}
-
- /**
- * Checks that the given file can be used for synthesis output.
- */
- private boolean checkFile(File file) {
- try {
- if (file.exists()) {
- Log.v(TAG, "File " + file + " exists, deleting.");
- if (!file.delete()) {
- Log.e(TAG, "Failed to delete " + file);
- return false;
- }
- }
- if (!file.createNewFile()) {
- Log.e(TAG, "Can't create file " + file);
- return false;
- }
- if (!file.delete()) {
- Log.e(TAG, "Failed to delete " + file);
- return false;
- }
- return true;
- } catch (IOException e) {
- Log.e(TAG, "Can't use " + file + " due to exception " + e);
- return false;
- }
- }
}
private class AudioSpeechItem extends SpeechItem {
diff --git a/core/java/android/test/AndroidTestCase.java b/core/java/android/test/AndroidTestCase.java
index 1015506..0c8cbe6 100644
--- a/core/java/android/test/AndroidTestCase.java
+++ b/core/java/android/test/AndroidTestCase.java
@@ -135,7 +135,8 @@ public class AndroidTestCase extends TestCase {
fail("expected SecurityException requiring " + permission);
} catch (SecurityException expected) {
assertNotNull("security exception's error message.", expected.getMessage());
- assertTrue("error message should contain " + permission + ".",
+ assertTrue("error message should contain \"" + permission + "\". Got: \""
+ + expected.getMessage() + "\".",
expected.getMessage().contains(permission));
}
}
diff --git a/core/java/android/text/Spanned.java b/core/java/android/text/Spanned.java
index 2b73763..b4622e0 100644
--- a/core/java/android/text/Spanned.java
+++ b/core/java/android/text/Spanned.java
@@ -30,8 +30,19 @@ extends CharSequence
* of spans.
*
* MARK and POINT are conceptually located <i>between</i> two adjacent characters.
- * A MARK is "attached" to the character on the left hand side, while a POINT
- * tends to stick to the character on the right hand side.
+ * A MARK is "attached" to the character before, while a POINT will stick to the character
+ * after. The insertion cursor is conceptually located between the MARK and the POINT.
+ *
+ * As a result, inserting a new character between a MARK and a POINT will leave the MARK
+ * unchanged, while the POINT will be shifted, now located after the inserted character and
+ * still glued to the same character after it.
+ *
+ * Depending on whether the insertion happens at the beginning or the end of a span, the span
+ * will hence be expanded to <i>include</i> the new character (when the span is using a MARK at
+ * its beginning or a POINT at its end) or it will be <i>excluded</i>.
+ *
+ * Note that <i>before</i> and <i>after</i> here refer to offsets in the String, which are
+ * independent from the visual representation of the text (left-to-right or right-to-left).
*/
public static final int SPAN_POINT_MARK_MASK = 0x33;
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 6a619af..9051285 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -150,8 +150,8 @@ public class StaticLayout extends Layout {
mColumns = COLUMNS_ELLIPSIZE;
mLines = new int[ArrayUtils.idealIntArraySize(2 * mColumns)];
- mLineDirections = new Directions[
- ArrayUtils.idealIntArraySize(2 * mColumns)];
+ mLineDirections = new Directions[ArrayUtils.idealIntArraySize(2 * mColumns)];
+ // FIXME This is never recycled
mMeasured = MeasuredText.obtain();
}
@@ -340,7 +340,9 @@ public class StaticLayout extends Layout {
w += widths[j - paraStart];
}
- if (w <= width) {
+ boolean isSpaceOrTab = c == CHAR_SPACE || c == CHAR_TAB;
+
+ if (w <= width || isSpaceOrTab) {
fitWidth = w;
fit = j + 1;
@@ -353,30 +355,17 @@ public class StaticLayout extends Layout {
if (fmBottom > fitBottom)
fitBottom = fmBottom;
- /*
- * From the Unicode Line Breaking Algorithm:
- * (at least approximately)
- *
- * .,:; are class IS: breakpoints
- * except when adjacent to digits
- * / is class SY: a breakpoint
- * except when followed by a digit.
- * - is class HY: a breakpoint
- * except when followed by a digit.
- *
- * Ideographs are class ID: breakpoints when adjacent,
- * except for NS (non-starters), which can be broken
- * after but not before.
- */
- if (c == CHAR_SPACE || c == CHAR_TAB ||
- ((c == CHAR_DOT || c == CHAR_COMMA ||
- c == CHAR_COLON || c == CHAR_SEMICOLON) &&
- (j - 1 < here || !Character.isDigit(chs[j - 1 - paraStart])) &&
- (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
- ((c == CHAR_SLASH || c == CHAR_HYPHEN) &&
- (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
- (c >= CHAR_FIRST_CJK && isIdeographic(c, true) &&
- j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false))) {
+ // From the Unicode Line Breaking Algorithm (at least approximately)
+ boolean isLineBreak = isSpaceOrTab ||
+ // / is class SY and - is class HY, except when followed by a digit
+ ((c == CHAR_SLASH || c == CHAR_HYPHEN) &&
+ (j + 1 >= spanEnd || !Character.isDigit(chs[j + 1 - paraStart]))) ||
+ // Ideographs are class ID: breakpoints when adjacent, except for NS
+ // (non-starters), which can be broken after but not before
+ (c >= CHAR_FIRST_CJK && isIdeographic(c, true) &&
+ j + 1 < spanEnd && isIdeographic(chs[j + 1 - paraStart], false));
+
+ if (isLineBreak) {
okWidth = w;
ok = j + 1;
@@ -396,13 +385,6 @@ public class StaticLayout extends Layout {
float currentTextWidth;
if (ok != here) {
- // If it is a space that makes the length exceed width, cut here
- if (c == CHAR_SPACE) ok = j + 1;
-
- while (ok < spanEnd && chs[ok - paraStart] == CHAR_SPACE) {
- ok++;
- }
-
endPos = ok;
above = okAscent;
below = okDescent;
@@ -450,10 +432,10 @@ public class StaticLayout extends Layout {
spanEnd = here;
break;
}
- }
- // FIXME This should be moved in the above else block which changes mLineCount
- if (mLineCount >= mMaximumVisibleLineCount) {
- break;
+
+ if (mLineCount >= mMaximumVisibleLineCount) {
+ break;
+ }
}
}
}
@@ -972,10 +954,6 @@ public class StaticLayout extends Layout {
private static final char CHAR_NEW_LINE = '\n';
private static final char CHAR_TAB = '\t';
private static final char CHAR_SPACE = ' ';
- private static final char CHAR_DOT = '.';
- private static final char CHAR_COMMA = ',';
- private static final char CHAR_COLON = ':';
- private static final char CHAR_SEMICOLON = ';';
private static final char CHAR_SLASH = '/';
private static final char CHAR_HYPHEN = '-';
diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java
index be2840b..df8c4c6 100644
--- a/core/java/android/text/TextDirectionHeuristics.java
+++ b/core/java/android/text/TextDirectionHeuristics.java
@@ -17,11 +17,11 @@
package android.text;
-import android.util.LocaleUtil;
import android.view.View;
/**
* Some objects that implement TextDirectionHeuristic.
+ *
* @hide
*/
public class TextDirectionHeuristics {
@@ -241,7 +241,7 @@ public class TextDirectionHeuristics {
@Override
protected boolean defaultIsRtl() {
- final int dir = LocaleUtil.getLayoutDirectionFromLocale(java.util.Locale.getDefault());
+ final int dir = TextUtils.getLayoutDirectionFromLocale(java.util.Locale.getDefault());
return (dir == View.LAYOUT_DIRECTION_RTL);
}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 270624c..1508d10 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -27,6 +27,7 @@ import android.text.style.CharacterStyle;
import android.text.style.EasyEditSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.LeadingMarginSpan;
+import android.text.style.LocaleSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.QuoteSpan;
import android.text.style.RelativeSizeSpan;
@@ -45,11 +46,14 @@ import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.util.Printer;
+import android.view.View;
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
+import libcore.icu.ICU;
import java.lang.reflect.Array;
import java.util.Iterator;
+import java.util.Locale;
import java.util.regex.Pattern;
public class TextUtils {
@@ -587,6 +591,8 @@ public class TextUtils {
public static final int SUGGESTION_RANGE_SPAN = 21;
/** @hide */
public static final int EASY_EDIT_SPAN = 22;
+ /** @hide */
+ public static final int LOCALE_SPAN = 23;
/**
* Flatten a CharSequence and whatever styles can be copied across processes
@@ -754,6 +760,10 @@ public class TextUtils {
readSpan(p, sp, new EasyEditSpan());
break;
+ case LOCALE_SPAN:
+ readSpan(p, sp, new LocaleSpan(p));
+ break;
+
default:
throw new RuntimeException("bogus span encoding " + kind);
}
@@ -1042,9 +1052,14 @@ public class TextUtils {
float avail, TruncateAt where,
boolean preserveLength,
EllipsizeCallback callback) {
+
+ final String ellipsis = (where == TruncateAt.END_SMALL) ?
+ Resources.getSystem().getString(R.string.ellipsis_two_dots) :
+ Resources.getSystem().getString(R.string.ellipsis);
+
return ellipsize(text, paint, avail, where, preserveLength, callback,
TextDirectionHeuristics.FIRSTSTRONG_LTR,
- (where == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL);
+ ellipsis);
}
/**
@@ -1694,15 +1709,64 @@ public class TextUtils {
return (int) (range & 0x00000000FFFFFFFFL);
}
+ /**
+ * Return the layout direction for a given Locale
+ *
+ * @param locale the Locale for which we want the layout direction. Can be null.
+ * @return the layout direction. This may be one of:
+ * {@link android.view.View#LAYOUT_DIRECTION_LTR} or
+ * {@link android.view.View#LAYOUT_DIRECTION_RTL}.
+ *
+ * Be careful: this code will need to be updated when vertical scripts will be supported
+ */
+ public static int getLayoutDirectionFromLocale(Locale locale) {
+ if (locale != null && !locale.equals(Locale.ROOT)) {
+ final String scriptSubtag = ICU.getScript(ICU.addLikelySubtags(locale.toString()));
+ if (scriptSubtag == null) return getLayoutDirectionFromFirstChar(locale);
+
+ if (scriptSubtag.equalsIgnoreCase(ARAB_SCRIPT_SUBTAG) ||
+ scriptSubtag.equalsIgnoreCase(HEBR_SCRIPT_SUBTAG)) {
+ return View.LAYOUT_DIRECTION_RTL;
+ }
+ }
+
+ return View.LAYOUT_DIRECTION_LTR;
+ }
+
+ /**
+ * Fallback algorithm to detect the locale direction. Rely on the fist char of the
+ * localized locale name. This will not work if the localized locale name is in English
+ * (this is the case for ICU 4.4 and "Urdu" script)
+ *
+ * @param locale
+ * @return the layout direction. This may be one of:
+ * {@link View#LAYOUT_DIRECTION_LTR} or
+ * {@link View#LAYOUT_DIRECTION_RTL}.
+ *
+ * Be careful: this code will need to be updated when vertical scripts will be supported
+ *
+ * @hide
+ */
+ private static int getLayoutDirectionFromFirstChar(Locale locale) {
+ switch(Character.getDirectionality(locale.getDisplayName(locale).charAt(0))) {
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+ return View.LAYOUT_DIRECTION_RTL;
+
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
+ default:
+ return View.LAYOUT_DIRECTION_LTR;
+ }
+ }
+
private static Object sLock = new Object();
+
private static char[] sTemp = null;
private static String[] EMPTY_STRING_ARRAY = new String[]{};
private static final char ZWNBS_CHAR = '\uFEFF';
- private static final String ELLIPSIS_NORMAL = Resources.getSystem().getString(
- R.string.ellipsis);
- private static final String ELLIPSIS_TWO_DOTS = Resources.getSystem().getString(
- R.string.ellipsis_two_dots);
+ private static String ARAB_SCRIPT_SUBTAG = "Arab";
+ private static String HEBR_SCRIPT_SUBTAG = "Hebr";
}
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index 524f941..c36273e 100644
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -35,10 +35,18 @@ import java.text.SimpleDateFormat;
Utility class for producing strings with formatted date/time.
<p>
- This class takes as inputs a format string and a representation of a date/time.
- The format string controls how the output is generated.
+ Most callers should avoid supplying their own format strings to this
+ class' {@code format} methods and rely on the correctly localized ones
+ supplied by the system. This class' factory methods return
+ appropriately-localized {@link java.text.DateFormat} instances, suitable
+ for both formatting and parsing dates. For the canonical documentation
+ of format strings, see {@link java.text.SimpleDateFormat}.
</p>
<p>
+ The format methods in this class takes as inputs a format string and a representation of a date/time.
+ The format string controls how the output is generated.
+ This class only supports a subset of the full Unicode specification.
+ Use {@link java.text.SimpleDateFormat} if you need more.
Formatting characters may be repeated in order to get more detailed representations
of that field. For instance, the format character &apos;M&apos; is used to
represent the month. Depending on how many times that character is repeated
@@ -152,7 +160,8 @@ public class DateFormat {
public static final char MINUTE = 'm';
/**
- This designator indicates the month of the year
+ This designator indicates the month of the year. See also
+ {@link #STANDALONE_MONTH}.
Examples for September:
M -> 9
@@ -163,6 +172,14 @@ public class DateFormat {
public static final char MONTH = 'M';
/**
+ This designator indicates the standalone month of the year,
+ necessary in some format strings in some languages. For
+ example, Russian distinguishes between the "June" in
+ "June" and that in "June 2010".
+ */
+ public static final char STANDALONE_MONTH = 'L';
+
+ /**
This designator indicates the seconds of the minute.
Examples for 7 seconds past the minute:
@@ -374,7 +391,7 @@ public class DateFormat {
index++;
}
- if (!foundMonth && (c == MONTH)) {
+ if (!foundMonth && (c == MONTH || c == STANDALONE_MONTH)) {
foundMonth = true;
order[index] = MONTH;
index++;
@@ -494,9 +511,10 @@ public class DateFormat {
break;
case MONTH:
- replacement = getMonthString(inDate, count);
+ case STANDALONE_MONTH:
+ replacement = getMonthString(inDate, count, c);
break;
-
+
case SECONDS:
replacement = zeroPad(inDate.get(Calendar.SECOND), count);
break;
@@ -527,14 +545,19 @@ public class DateFormat {
return s.toString();
}
- private static final String getMonthString(Calendar inDate, int count) {
+ private static final String getMonthString(Calendar inDate, int count, int kind) {
+ boolean standalone = (kind == STANDALONE_MONTH);
int month = inDate.get(Calendar.MONTH);
- if (count >= 4)
- return DateUtils.getMonthString(month, DateUtils.LENGTH_LONG);
- else if (count == 3)
- return DateUtils.getMonthString(month, DateUtils.LENGTH_MEDIUM);
- else {
+ if (count >= 4) {
+ return standalone
+ ? DateUtils.getStandaloneMonthString(month, DateUtils.LENGTH_LONG)
+ : DateUtils.getMonthString(month, DateUtils.LENGTH_LONG);
+ } else if (count == 3) {
+ return standalone
+ ? DateUtils.getStandaloneMonthString(month, DateUtils.LENGTH_MEDIUM)
+ : DateUtils.getMonthString(month, DateUtils.LENGTH_MEDIUM);
+ } else {
// Calendar.JANUARY == 0, so add 1 to month.
return zeroPad(month+1, count);
}
@@ -574,7 +597,8 @@ public class DateFormat {
private static final String getYearString(Calendar inDate, int count) {
int year = inDate.get(Calendar.YEAR);
- return (count <= 2) ? zeroPad(year % 100, 2) : String.valueOf(year);
+ return (count <= 2) ? zeroPad(year % 100, 2)
+ : String.format(Locale.getDefault(), "%d", year);
}
private static final int appendQuotedText(SpannableStringBuilder s, int i, int len) {
@@ -615,17 +639,6 @@ public class DateFormat {
}
private static final String zeroPad(int inValue, int inMinDigits) {
- String val = String.valueOf(inValue);
-
- if (val.length() < inMinDigits) {
- char[] buf = new char[inMinDigits];
-
- for (int i = 0; i < inMinDigits; i++)
- buf[i] = '0';
-
- val.getChars(0, val.length(), buf, inMinDigits - val.length());
- val = new String(buf);
- }
- return val;
+ return String.format(Locale.getDefault(), "%0" + inMinDigits + "d", inValue);
}
}
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index 2e962a0..1060bd8 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -29,6 +29,8 @@ import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
+import libcore.icu.LocaleData;
+
/**
* This class contains various date-related utilities for creating text for things like
* elapsed time and date ranges, strings for days of the week and months, and AM/PM text etc.
@@ -36,102 +38,6 @@ import java.util.TimeZone;
public class DateUtils
{
private static final Object sLock = new Object();
- private static final int[] sDaysLong = new int[] {
- com.android.internal.R.string.day_of_week_long_sunday,
- com.android.internal.R.string.day_of_week_long_monday,
- com.android.internal.R.string.day_of_week_long_tuesday,
- com.android.internal.R.string.day_of_week_long_wednesday,
- com.android.internal.R.string.day_of_week_long_thursday,
- com.android.internal.R.string.day_of_week_long_friday,
- com.android.internal.R.string.day_of_week_long_saturday,
- };
- private static final int[] sDaysMedium = new int[] {
- com.android.internal.R.string.day_of_week_medium_sunday,
- com.android.internal.R.string.day_of_week_medium_monday,
- com.android.internal.R.string.day_of_week_medium_tuesday,
- com.android.internal.R.string.day_of_week_medium_wednesday,
- com.android.internal.R.string.day_of_week_medium_thursday,
- com.android.internal.R.string.day_of_week_medium_friday,
- com.android.internal.R.string.day_of_week_medium_saturday,
- };
- private static final int[] sDaysShort = new int[] {
- com.android.internal.R.string.day_of_week_short_sunday,
- com.android.internal.R.string.day_of_week_short_monday,
- com.android.internal.R.string.day_of_week_short_tuesday,
- com.android.internal.R.string.day_of_week_short_wednesday,
- com.android.internal.R.string.day_of_week_short_thursday,
- com.android.internal.R.string.day_of_week_short_friday,
- com.android.internal.R.string.day_of_week_short_saturday,
- };
- private static final int[] sDaysShortest = new int[] {
- com.android.internal.R.string.day_of_week_shortest_sunday,
- com.android.internal.R.string.day_of_week_shortest_monday,
- com.android.internal.R.string.day_of_week_shortest_tuesday,
- com.android.internal.R.string.day_of_week_shortest_wednesday,
- com.android.internal.R.string.day_of_week_shortest_thursday,
- com.android.internal.R.string.day_of_week_shortest_friday,
- com.android.internal.R.string.day_of_week_shortest_saturday,
- };
- private static final int[] sMonthsStandaloneLong = new int [] {
- com.android.internal.R.string.month_long_standalone_january,
- com.android.internal.R.string.month_long_standalone_february,
- com.android.internal.R.string.month_long_standalone_march,
- com.android.internal.R.string.month_long_standalone_april,
- com.android.internal.R.string.month_long_standalone_may,
- com.android.internal.R.string.month_long_standalone_june,
- com.android.internal.R.string.month_long_standalone_july,
- com.android.internal.R.string.month_long_standalone_august,
- com.android.internal.R.string.month_long_standalone_september,
- com.android.internal.R.string.month_long_standalone_october,
- com.android.internal.R.string.month_long_standalone_november,
- com.android.internal.R.string.month_long_standalone_december,
- };
- private static final int[] sMonthsLong = new int [] {
- com.android.internal.R.string.month_long_january,
- com.android.internal.R.string.month_long_february,
- com.android.internal.R.string.month_long_march,
- com.android.internal.R.string.month_long_april,
- com.android.internal.R.string.month_long_may,
- com.android.internal.R.string.month_long_june,
- com.android.internal.R.string.month_long_july,
- com.android.internal.R.string.month_long_august,
- com.android.internal.R.string.month_long_september,
- com.android.internal.R.string.month_long_october,
- com.android.internal.R.string.month_long_november,
- com.android.internal.R.string.month_long_december,
- };
- private static final int[] sMonthsMedium = new int [] {
- com.android.internal.R.string.month_medium_january,
- com.android.internal.R.string.month_medium_february,
- com.android.internal.R.string.month_medium_march,
- com.android.internal.R.string.month_medium_april,
- com.android.internal.R.string.month_medium_may,
- com.android.internal.R.string.month_medium_june,
- com.android.internal.R.string.month_medium_july,
- com.android.internal.R.string.month_medium_august,
- com.android.internal.R.string.month_medium_september,
- com.android.internal.R.string.month_medium_october,
- com.android.internal.R.string.month_medium_november,
- com.android.internal.R.string.month_medium_december,
- };
- private static final int[] sMonthsShortest = new int [] {
- com.android.internal.R.string.month_shortest_january,
- com.android.internal.R.string.month_shortest_february,
- com.android.internal.R.string.month_shortest_march,
- com.android.internal.R.string.month_shortest_april,
- com.android.internal.R.string.month_shortest_may,
- com.android.internal.R.string.month_shortest_june,
- com.android.internal.R.string.month_shortest_july,
- com.android.internal.R.string.month_shortest_august,
- com.android.internal.R.string.month_shortest_september,
- com.android.internal.R.string.month_shortest_october,
- com.android.internal.R.string.month_shortest_november,
- com.android.internal.R.string.month_shortest_december,
- };
- private static final int[] sAmPm = new int[] {
- com.android.internal.R.string.am,
- com.android.internal.R.string.pm,
- };
private static Configuration sLastConfig;
private static java.text.DateFormat sStatusTimeFormat;
private static String sElapsedFormatMMSS;
@@ -139,7 +45,6 @@ public class DateUtils
private static final String FAST_FORMAT_HMMSS = "%1$d:%2$02d:%3$02d";
private static final String FAST_FORMAT_MMSS = "%1$02d:%2$02d";
- private static final char TIME_PADDING = '0';
private static final char TIME_SEPARATOR = ':';
@@ -336,18 +241,17 @@ public class DateUtils
*/
@Deprecated
public static String getDayOfWeekString(int dayOfWeek, int abbrev) {
- int[] list;
+ LocaleData d = LocaleData.get(Locale.getDefault());
+ String[] names;
switch (abbrev) {
- case LENGTH_LONG: list = sDaysLong; break;
- case LENGTH_MEDIUM: list = sDaysMedium; break;
- case LENGTH_SHORT: list = sDaysShort; break;
- case LENGTH_SHORTER: list = sDaysShort; break;
- case LENGTH_SHORTEST: list = sDaysShortest; break;
- default: list = sDaysMedium; break;
+ case LENGTH_LONG: names = d.longWeekdayNames; break;
+ case LENGTH_MEDIUM: names = d.shortWeekdayNames; break;
+ case LENGTH_SHORT: names = d.shortWeekdayNames; break; // TODO
+ case LENGTH_SHORTER: names = d.shortWeekdayNames; break; // TODO
+ case LENGTH_SHORTEST: names = d.tinyWeekdayNames; break;
+ default: names = d.shortWeekdayNames; break;
}
-
- Resources r = Resources.getSystem();
- return r.getString(list[dayOfWeek - Calendar.SUNDAY]);
+ return names[dayOfWeek];
}
/**
@@ -359,8 +263,7 @@ public class DateUtils
*/
@Deprecated
public static String getAMPMString(int ampm) {
- Resources r = Resources.getSystem();
- return r.getString(sAmPm[ampm - Calendar.AM]);
+ return LocaleData.get(Locale.getDefault()).amPm[ampm - Calendar.AM];
}
/**
@@ -376,22 +279,21 @@ public class DateUtils
*/
@Deprecated
public static String getMonthString(int month, int abbrev) {
- // Note that here we use sMonthsMedium for MEDIUM, SHORT and SHORTER.
+ // Note that here we use d.shortMonthNames for MEDIUM, SHORT and SHORTER.
// This is a shortcut to not spam the translators with too many variations
// of the same string. If we find that in a language the distinction
// is necessary, we can can add more without changing this API.
- int[] list;
+ LocaleData d = LocaleData.get(Locale.getDefault());
+ String[] names;
switch (abbrev) {
- case LENGTH_LONG: list = sMonthsLong; break;
- case LENGTH_MEDIUM: list = sMonthsMedium; break;
- case LENGTH_SHORT: list = sMonthsMedium; break;
- case LENGTH_SHORTER: list = sMonthsMedium; break;
- case LENGTH_SHORTEST: list = sMonthsShortest; break;
- default: list = sMonthsMedium; break;
+ case LENGTH_LONG: names = d.longMonthNames; break;
+ case LENGTH_MEDIUM: names = d.shortMonthNames; break;
+ case LENGTH_SHORT: names = d.shortMonthNames; break;
+ case LENGTH_SHORTER: names = d.shortMonthNames; break;
+ case LENGTH_SHORTEST: names = d.tinyMonthNames; break;
+ default: names = d.shortMonthNames; break;
}
-
- Resources r = Resources.getSystem();
- return r.getString(list[month - Calendar.JANUARY]);
+ return names[month];
}
/**
@@ -411,23 +313,22 @@ public class DateUtils
*/
@Deprecated
public static String getStandaloneMonthString(int month, int abbrev) {
- // Note that here we use sMonthsMedium for MEDIUM, SHORT and SHORTER.
+ // Note that here we use d.shortMonthNames for MEDIUM, SHORT and SHORTER.
// This is a shortcut to not spam the translators with too many variations
// of the same string. If we find that in a language the distinction
// is necessary, we can can add more without changing this API.
- int[] list;
+ LocaleData d = LocaleData.get(Locale.getDefault());
+ String[] names;
switch (abbrev) {
- case LENGTH_LONG: list = sMonthsStandaloneLong;
+ case LENGTH_LONG: names = d.longStandAloneMonthNames;
break;
- case LENGTH_MEDIUM: list = sMonthsMedium; break;
- case LENGTH_SHORT: list = sMonthsMedium; break;
- case LENGTH_SHORTER: list = sMonthsMedium; break;
- case LENGTH_SHORTEST: list = sMonthsShortest; break;
- default: list = sMonthsMedium; break;
+ case LENGTH_MEDIUM: names = d.shortMonthNames; break;
+ case LENGTH_SHORT: names = d.shortMonthNames; break;
+ case LENGTH_SHORTER: names = d.shortMonthNames; break;
+ case LENGTH_SHORTEST: names = d.tinyMonthNames; break;
+ default: names = d.shortMonthNames; break;
}
-
- Resources r = Resources.getSystem();
- return r.getString(list[month - Calendar.JANUARY]);
+ return names[month];
}
/**
@@ -650,14 +551,19 @@ public class DateUtils
int days = Math.abs(currentDay - startDay);
boolean past = (today > day);
+ // TODO: some locales name other days too, such as de_DE's "Vorgestern" (today - 2).
+ Locale locale = r.getConfiguration().locale;
+ if (locale == null) {
+ locale = Locale.getDefault();
+ }
if (days == 1) {
if (past) {
- return r.getString(com.android.internal.R.string.yesterday);
+ return LocaleData.get(locale).yesterday;
} else {
- return r.getString(com.android.internal.R.string.tomorrow);
+ return LocaleData.get(locale).tomorrow;
}
} else if (days == 0) {
- return r.getString(com.android.internal.R.string.today);
+ return LocaleData.get(locale).today;
}
int resId;
@@ -741,33 +647,36 @@ public class DateUtils
}
}
+ private static void append(StringBuilder sb, long value, boolean pad, char zeroDigit) {
+ if (value < 10) {
+ if (pad) {
+ sb.append(zeroDigit);
+ }
+ } else {
+ sb.append((char) (zeroDigit + (value / 10)));
+ }
+ sb.append((char) (zeroDigit + (value % 10)));
+ }
+
/**
- * Fast formatting of h:mm:ss
+ * Fast formatting of h:mm:ss.
*/
private static String formatElapsedTime(StringBuilder recycle, String format, long hours,
long minutes, long seconds) {
if (FAST_FORMAT_HMMSS.equals(format)) {
+ char zeroDigit = LocaleData.get(Locale.getDefault()).zeroDigit;
+
StringBuilder sb = recycle;
if (sb == null) {
sb = new StringBuilder(8);
} else {
sb.setLength(0);
}
- sb.append(hours);
+ append(sb, hours, false, zeroDigit);
sb.append(TIME_SEPARATOR);
- if (minutes < 10) {
- sb.append(TIME_PADDING);
- } else {
- sb.append(toDigitChar(minutes / 10));
- }
- sb.append(toDigitChar(minutes % 10));
+ append(sb, minutes, true, zeroDigit);
sb.append(TIME_SEPARATOR);
- if (seconds < 10) {
- sb.append(TIME_PADDING);
- } else {
- sb.append(toDigitChar(seconds / 10));
- }
- sb.append(toDigitChar(seconds % 10));
+ append(sb, seconds, true, zeroDigit);
return sb.toString();
} else {
return String.format(format, hours, minutes, seconds);
@@ -775,40 +684,28 @@ public class DateUtils
}
/**
- * Fast formatting of m:ss
+ * Fast formatting of mm:ss.
*/
private static String formatElapsedTime(StringBuilder recycle, String format, long minutes,
long seconds) {
if (FAST_FORMAT_MMSS.equals(format)) {
+ char zeroDigit = LocaleData.get(Locale.getDefault()).zeroDigit;
+
StringBuilder sb = recycle;
if (sb == null) {
sb = new StringBuilder(8);
} else {
sb.setLength(0);
}
- if (minutes < 10) {
- sb.append(TIME_PADDING);
- } else {
- sb.append(toDigitChar(minutes / 10));
- }
- sb.append(toDigitChar(minutes % 10));
+ append(sb, minutes, false, zeroDigit);
sb.append(TIME_SEPARATOR);
- if (seconds < 10) {
- sb.append(TIME_PADDING);
- } else {
- sb.append(toDigitChar(seconds / 10));
- }
- sb.append(toDigitChar(seconds % 10));
+ append(sb, seconds, true, zeroDigit);
return sb.toString();
} else {
return String.format(format, minutes, seconds);
}
}
- private static char toDigitChar(long digit) {
- return (char) (digit + '0');
- }
-
/**
* Format a date / time such that if the then is on the same day as now, it shows
* just the time and if it's a different day, it shows just the date.
@@ -1480,6 +1377,14 @@ public class DateUtils
String endMonthDayString = isInstant ? null : endDate.format(MONTH_DAY_FORMAT);
String endYearString = isInstant ? null : endDate.format(YEAR_FORMAT);
+ String startStandaloneMonthString = startMonthString;
+ String endStandaloneMonthString = endMonthString;
+ // We need standalone months for these strings in Persian (fa): http://b/6811327
+ if (!numericDate && !abbrevMonth && Locale.getDefault().getLanguage().equals("fa")) {
+ startStandaloneMonthString = startDate.format("%-B");
+ endStandaloneMonthString = endDate.format("%-B");
+ }
+
if (startMonthNum != endMonthNum) {
// Same year, different month.
// Example: "October 28 - November 3"
@@ -1500,7 +1405,8 @@ public class DateUtils
startWeekDayString, startMonthString, startMonthDayString,
startYearString, startTimeString,
endWeekDayString, endMonthString, endMonthDayString,
- endYearString, endTimeString);
+ endYearString, endTimeString,
+ startStandaloneMonthString, endStandaloneMonthString);
}
if (startDay != endDay) {
@@ -1519,7 +1425,8 @@ public class DateUtils
startWeekDayString, startMonthString, startMonthDayString,
startYearString, startTimeString,
endWeekDayString, endMonthString, endMonthDayString,
- endYearString, endTimeString);
+ endYearString, endTimeString,
+ startStandaloneMonthString, endStandaloneMonthString);
}
// Same start and end day
diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java
index e9b0d32..5ef86b1 100644
--- a/core/java/android/text/format/Time.java
+++ b/core/java/android/text/format/Time.java
@@ -21,6 +21,8 @@ import android.content.res.Resources;
import java.util.Locale;
import java.util.TimeZone;
+import libcore.icu.LocaleData;
+
/**
* An alternative to the {@link java.util.Calendar} and
* {@link java.util.GregorianCalendar} classes. An instance of the Time class represents
@@ -149,6 +151,9 @@ public class Time {
private static String sDateTimeFormat;
private static String sAm;
private static String sPm;
+ private static char sZeroDigit;
+
+ // Referenced by native code.
private static String sDateCommand = "%a %b %e %H:%M:%S %Z %Y";
/**
@@ -317,83 +322,52 @@ public class Time {
Locale locale = Locale.getDefault();
if (sLocale == null || locale == null || !(locale.equals(sLocale))) {
- Resources r = Resources.getSystem();
+ LocaleData localeData = LocaleData.get(locale);
+
+ sAm = localeData.amPm[0];
+ sPm = localeData.amPm[1];
+ sZeroDigit = localeData.zeroDigit;
- sShortMonths = new String[] {
- r.getString(com.android.internal.R.string.month_medium_january),
- r.getString(com.android.internal.R.string.month_medium_february),
- r.getString(com.android.internal.R.string.month_medium_march),
- r.getString(com.android.internal.R.string.month_medium_april),
- r.getString(com.android.internal.R.string.month_medium_may),
- r.getString(com.android.internal.R.string.month_medium_june),
- r.getString(com.android.internal.R.string.month_medium_july),
- r.getString(com.android.internal.R.string.month_medium_august),
- r.getString(com.android.internal.R.string.month_medium_september),
- r.getString(com.android.internal.R.string.month_medium_october),
- r.getString(com.android.internal.R.string.month_medium_november),
- r.getString(com.android.internal.R.string.month_medium_december),
- };
- sLongMonths = new String[] {
- r.getString(com.android.internal.R.string.month_long_january),
- r.getString(com.android.internal.R.string.month_long_february),
- r.getString(com.android.internal.R.string.month_long_march),
- r.getString(com.android.internal.R.string.month_long_april),
- r.getString(com.android.internal.R.string.month_long_may),
- r.getString(com.android.internal.R.string.month_long_june),
- r.getString(com.android.internal.R.string.month_long_july),
- r.getString(com.android.internal.R.string.month_long_august),
- r.getString(com.android.internal.R.string.month_long_september),
- r.getString(com.android.internal.R.string.month_long_october),
- r.getString(com.android.internal.R.string.month_long_november),
- r.getString(com.android.internal.R.string.month_long_december),
- };
- sLongStandaloneMonths = new String[] {
- r.getString(com.android.internal.R.string.month_long_standalone_january),
- r.getString(com.android.internal.R.string.month_long_standalone_february),
- r.getString(com.android.internal.R.string.month_long_standalone_march),
- r.getString(com.android.internal.R.string.month_long_standalone_april),
- r.getString(com.android.internal.R.string.month_long_standalone_may),
- r.getString(com.android.internal.R.string.month_long_standalone_june),
- r.getString(com.android.internal.R.string.month_long_standalone_july),
- r.getString(com.android.internal.R.string.month_long_standalone_august),
- r.getString(com.android.internal.R.string.month_long_standalone_september),
- r.getString(com.android.internal.R.string.month_long_standalone_october),
- r.getString(com.android.internal.R.string.month_long_standalone_november),
- r.getString(com.android.internal.R.string.month_long_standalone_december),
- };
- sShortWeekdays = new String[] {
- r.getString(com.android.internal.R.string.day_of_week_medium_sunday),
- r.getString(com.android.internal.R.string.day_of_week_medium_monday),
- r.getString(com.android.internal.R.string.day_of_week_medium_tuesday),
- r.getString(com.android.internal.R.string.day_of_week_medium_wednesday),
- r.getString(com.android.internal.R.string.day_of_week_medium_thursday),
- r.getString(com.android.internal.R.string.day_of_week_medium_friday),
- r.getString(com.android.internal.R.string.day_of_week_medium_saturday),
- };
- sLongWeekdays = new String[] {
- r.getString(com.android.internal.R.string.day_of_week_long_sunday),
- r.getString(com.android.internal.R.string.day_of_week_long_monday),
- r.getString(com.android.internal.R.string.day_of_week_long_tuesday),
- r.getString(com.android.internal.R.string.day_of_week_long_wednesday),
- r.getString(com.android.internal.R.string.day_of_week_long_thursday),
- r.getString(com.android.internal.R.string.day_of_week_long_friday),
- r.getString(com.android.internal.R.string.day_of_week_long_saturday),
- };
+ sShortMonths = localeData.shortMonthNames;
+ sLongMonths = localeData.longMonthNames;
+ sLongStandaloneMonths = localeData.longStandAloneMonthNames;
+ sShortWeekdays = localeData.shortWeekdayNames;
+ sLongWeekdays = localeData.longWeekdayNames;
+
+ Resources r = Resources.getSystem();
sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day);
sDateOnlyFormat = r.getString(com.android.internal.R.string.month_day_year);
sDateTimeFormat = r.getString(com.android.internal.R.string.date_and_time);
- sAm = r.getString(com.android.internal.R.string.am);
- sPm = r.getString(com.android.internal.R.string.pm);
sLocale = locale;
}
- return format1(format);
+ String result = format1(format);
+ if (sZeroDigit != '0') {
+ result = localizeDigits(result);
+ }
+ return result;
}
}
native private String format1(String format);
+ // TODO: unify this with java.util.Formatter's copy.
+ private String localizeDigits(String s) {
+ int length = s.length();
+ int offsetToLocalizedDigits = sZeroDigit - '0';
+ StringBuilder result = new StringBuilder(length);
+ for (int i = 0; i < length; ++i) {
+ char ch = s.charAt(i);
+ if (ch >= '0' && ch <= '9') {
+ ch += offsetToLocalizedDigits;
+ }
+ result.append(ch);
+ }
+ return result.toString();
+ }
+
+
/**
* Return the current time in YYYYMMDDTHHMMSS<tz> format
*/
@@ -437,6 +411,9 @@ public class Time {
* @throws android.util.TimeFormatException if s cannot be parsed.
*/
public boolean parse(String s) {
+ if (s == null) {
+ throw new NullPointerException("time string is null");
+ }
if (nativeParse(s)) {
timezone = TIMEZONE_UTC;
return true;
@@ -720,7 +697,7 @@ public class Time {
int minutes = (offset % 3600) / 60;
int hours = offset / 3600;
- return String.format("%s%s%02d:%02d", base, sign, hours, minutes);
+ return String.format(Locale.US, "%s%s%02d:%02d", base, sign, hours, minutes);
}
}
diff --git a/core/java/android/text/style/LocaleSpan.java b/core/java/android/text/style/LocaleSpan.java
new file mode 100644
index 0000000..a12c42f
--- /dev/null
+++ b/core/java/android/text/style/LocaleSpan.java
@@ -0,0 +1,84 @@
+/*
+ * 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.text.style;
+
+import android.graphics.Paint;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import java.util.Locale;
+
+/**
+ * Changes the {@link Locale} of the text to which the span is attached.
+ */
+public class LocaleSpan extends MetricAffectingSpan implements ParcelableSpan {
+ private final Locale mLocale;
+
+ /**
+ * Creates a LocaleSpan.
+ * @param locale The {@link Locale} of the text to which the span is
+ * attached.
+ */
+ public LocaleSpan(Locale locale) {
+ mLocale = locale;
+ }
+
+ public LocaleSpan(Parcel src) {
+ mLocale = new Locale(src.readString(), src.readString(), src.readString());
+ }
+
+ @Override
+ public int getSpanTypeId() {
+ return TextUtils.LOCALE_SPAN;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mLocale.getLanguage());
+ dest.writeString(mLocale.getCountry());
+ dest.writeString(mLocale.getVariant());
+ }
+
+ /**
+ * Returns the {@link Locale}.
+ *
+ * @return The {@link Locale} for this span.
+ */
+ public Locale getLocale() {
+ return mLocale;
+ }
+
+ @Override
+ public void updateDrawState(TextPaint ds) {
+ apply(ds, mLocale);
+ }
+
+ @Override
+ public void updateMeasureState(TextPaint paint) {
+ apply(paint, mLocale);
+ }
+
+ private static void apply(Paint paint, Locale locale) {
+ paint.setTextLocale(locale);
+ }
+}
diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
new file mode 100644
index 0000000..4fca570
--- /dev/null
+++ b/core/java/android/util/AtomicFile.java
@@ -0,0 +1,233 @@
+/*
+ * 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.util;
+
+import android.os.FileUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Helper class for performing atomic operations on a file by creating a
+ * backup file until a write has successfully completed. If you need this
+ * on older versions of the platform you can use
+ * {@link android.support.v4.util.AtomicFile} in the v4 support library.
+ * <p>
+ * Atomic file guarantees file integrity by ensuring that a file has
+ * been completely written and sync'd to disk before removing its backup.
+ * As long as the backup file exists, the original file is considered
+ * to be invalid (left over from a previous attempt to write the file).
+ * </p><p>
+ * Atomic file does not confer any file locking semantics.
+ * Do not use this class when the file may be accessed or modified concurrently
+ * by multiple threads or processes. The caller is responsible for ensuring
+ * appropriate mutual exclusion invariants whenever it accesses the file.
+ * </p>
+ */
+public class AtomicFile {
+ private final File mBaseName;
+ private final File mBackupName;
+
+ /**
+ * Create a new AtomicFile for a file located at the given File path.
+ * The secondary backup file will be the same file path with ".bak" appended.
+ */
+ public AtomicFile(File baseName) {
+ mBaseName = baseName;
+ mBackupName = new File(baseName.getPath() + ".bak");
+ }
+
+ /**
+ * Return the path to the base file. You should not generally use this,
+ * as the data at that path may not be valid.
+ */
+ public File getBaseFile() {
+ return mBaseName;
+ }
+
+ /**
+ * Delete the atomic file. This deletes both the base and backup files.
+ */
+ public void delete() {
+ mBaseName.delete();
+ mBackupName.delete();
+ }
+
+ /**
+ * Start a new write operation on the file. This returns a FileOutputStream
+ * to which you can write the new file data. The existing file is replaced
+ * with the new data. You <em>must not</em> directly close the given
+ * FileOutputStream; instead call either {@link #finishWrite(FileOutputStream)}
+ * or {@link #failWrite(FileOutputStream)}.
+ *
+ * <p>Note that if another thread is currently performing
+ * a write, this will simply replace whatever that thread is writing
+ * with the new file being written by this thread, and when the other
+ * thread finishes the write the new write operation will no longer be
+ * safe (or will be lost). You must do your own threading protection for
+ * access to AtomicFile.
+ */
+ public FileOutputStream startWrite() throws IOException {
+ // Rename the current file so it may be used as a backup during the next read
+ if (mBaseName.exists()) {
+ if (!mBackupName.exists()) {
+ if (!mBaseName.renameTo(mBackupName)) {
+ Log.w("AtomicFile", "Couldn't rename file " + mBaseName
+ + " to backup file " + mBackupName);
+ }
+ } else {
+ mBaseName.delete();
+ }
+ }
+ FileOutputStream str = null;
+ try {
+ str = new FileOutputStream(mBaseName);
+ } catch (FileNotFoundException e) {
+ File parent = mBaseName.getParentFile();
+ if (!parent.mkdir()) {
+ throw new IOException("Couldn't create directory " + mBaseName);
+ }
+ FileUtils.setPermissions(
+ parent.getPath(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+ -1, -1);
+ try {
+ str = new FileOutputStream(mBaseName);
+ } catch (FileNotFoundException e2) {
+ throw new IOException("Couldn't create " + mBaseName);
+ }
+ }
+ return str;
+ }
+
+ /**
+ * Call when you have successfully finished writing to the stream
+ * returned by {@link #startWrite()}. This will close, sync, and
+ * commit the new data. The next attempt to read the atomic file
+ * will return the new file stream.
+ */
+ public void finishWrite(FileOutputStream str) {
+ if (str != null) {
+ FileUtils.sync(str);
+ try {
+ str.close();
+ mBackupName.delete();
+ } catch (IOException e) {
+ Log.w("AtomicFile", "finishWrite: Got exception:", e);
+ }
+ }
+ }
+
+ /**
+ * Call when you have failed for some reason at writing to the stream
+ * returned by {@link #startWrite()}. This will close the current
+ * write stream, and roll back to the previous state of the file.
+ */
+ public void failWrite(FileOutputStream str) {
+ if (str != null) {
+ FileUtils.sync(str);
+ try {
+ str.close();
+ mBaseName.delete();
+ mBackupName.renameTo(mBaseName);
+ } catch (IOException e) {
+ Log.w("AtomicFile", "failWrite: Got exception:", e);
+ }
+ }
+ }
+
+ /** @hide
+ * @deprecated This is not safe.
+ */
+ @Deprecated public void truncate() throws IOException {
+ try {
+ FileOutputStream fos = new FileOutputStream(mBaseName);
+ FileUtils.sync(fos);
+ fos.close();
+ } catch (FileNotFoundException e) {
+ throw new IOException("Couldn't append " + mBaseName);
+ } catch (IOException e) {
+ }
+ }
+
+ /** @hide
+ * @deprecated This is not safe.
+ */
+ @Deprecated public FileOutputStream openAppend() throws IOException {
+ try {
+ return new FileOutputStream(mBaseName, true);
+ } catch (FileNotFoundException e) {
+ throw new IOException("Couldn't append " + mBaseName);
+ }
+ }
+
+ /**
+ * Open the atomic file for reading. If there previously was an
+ * incomplete write, this will roll back to the last good data before
+ * opening for read. You should call close() on the FileInputStream when
+ * you are done reading from it.
+ *
+ * <p>Note that if another thread is currently performing
+ * a write, this will incorrectly consider it to be in the state of a bad
+ * write and roll back, causing the new data currently being written to
+ * be dropped. You must do your own threading protection for access to
+ * AtomicFile.
+ */
+ public FileInputStream openRead() throws FileNotFoundException {
+ if (mBackupName.exists()) {
+ mBaseName.delete();
+ mBackupName.renameTo(mBaseName);
+ }
+ return new FileInputStream(mBaseName);
+ }
+
+ /**
+ * A convenience for {@link #openRead()} that also reads all of the
+ * file contents into a byte array which is returned.
+ */
+ public byte[] readFully() throws IOException {
+ FileInputStream stream = openRead();
+ try {
+ int pos = 0;
+ int avail = stream.available();
+ byte[] data = new byte[avail];
+ while (true) {
+ int amt = stream.read(data, pos, data.length-pos);
+ //Log.i("foo", "Read " + amt + " bytes at " + pos
+ // + " of avail " + data.length);
+ if (amt <= 0) {
+ //Log.i("foo", "**** FINISHED READING: pos=" + pos
+ // + " len=" + data.length);
+ return data;
+ }
+ pos += amt;
+ avail = stream.available();
+ if (avail > data.length-pos) {
+ byte[] newData = new byte[pos+avail];
+ System.arraycopy(data, 0, newData, 0, pos);
+ data = newData;
+ }
+ }
+ } finally {
+ stream.close();
+ }
+ }
+}
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 80da0b2..85e4b9d 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -79,11 +79,20 @@ public class DisplayMetrics {
public static final int DENSITY_DEFAULT = DENSITY_MEDIUM;
/**
+ * Scaling factor to convert a density in DPI units to the density scale.
+ * @hide
+ */
+ public static final float DENSITY_DEFAULT_SCALE = 1.0f / DENSITY_DEFAULT;
+
+ /**
* The device's density.
- * @hide becase eventually this should be able to change while
+ * @hide because eventually this should be able to change while
* running, so shouldn't be a constant.
+ * @deprecated There is no longer a static density; you can find the
+ * density for a display in {@link #densityDpi}.
*/
- public static final int DENSITY_DEVICE = getDeviceDensity();
+ @Deprecated
+ public static int DENSITY_DEVICE = getDeviceDensity();
/**
* The absolute width of the display in pixels.
@@ -150,6 +159,12 @@ public class DisplayMetrics {
*/
public float noncompatDensity;
/**
+ * The reported display density prior to any compatibility mode scaling
+ * being applied.
+ * @hide
+ */
+ public int noncompatDensityDpi;
+ /**
* The reported scaled density prior to any compatibility mode scaling
* being applied.
* @hide
@@ -182,6 +197,7 @@ public class DisplayMetrics {
noncompatWidthPixels = o.noncompatWidthPixels;
noncompatHeightPixels = o.noncompatHeightPixels;
noncompatDensity = o.noncompatDensity;
+ noncompatDensityDpi = o.noncompatDensityDpi;
noncompatScaledDensity = o.noncompatScaledDensity;
noncompatXdpi = o.noncompatXdpi;
noncompatYdpi = o.noncompatYdpi;
@@ -190,13 +206,52 @@ public class DisplayMetrics {
public void setToDefaults() {
widthPixels = 0;
heightPixels = 0;
- density = DENSITY_DEVICE / (float) DENSITY_DEFAULT;
- densityDpi = DENSITY_DEVICE;
+ density = DENSITY_DEVICE / (float) DENSITY_DEFAULT;
+ densityDpi = DENSITY_DEVICE;
scaledDensity = density;
xdpi = DENSITY_DEVICE;
ydpi = DENSITY_DEVICE;
- noncompatWidthPixels = 0;
- noncompatHeightPixels = 0;
+ noncompatWidthPixels = widthPixels;
+ noncompatHeightPixels = heightPixels;
+ noncompatDensity = density;
+ noncompatDensityDpi = densityDpi;
+ noncompatScaledDensity = scaledDensity;
+ noncompatXdpi = xdpi;
+ noncompatYdpi = ydpi;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof DisplayMetrics && equals((DisplayMetrics)o);
+ }
+
+ /**
+ * Returns true if these display metrics equal the other display metrics.
+ *
+ * @param other The display metrics with which to compare.
+ * @return True if the display metrics are equal.
+ */
+ public boolean equals(DisplayMetrics other) {
+ return other != null
+ && widthPixels == other.widthPixels
+ && heightPixels == other.heightPixels
+ && density == other.density
+ && densityDpi == other.densityDpi
+ && scaledDensity == other.scaledDensity
+ && xdpi == other.xdpi
+ && ydpi == other.ydpi
+ && noncompatWidthPixels == other.noncompatWidthPixels
+ && noncompatHeightPixels == other.noncompatHeightPixels
+ && noncompatDensity == other.noncompatDensity
+ && noncompatDensityDpi == other.noncompatDensityDpi
+ && noncompatScaledDensity == other.noncompatScaledDensity
+ && noncompatXdpi == other.noncompatXdpi
+ && noncompatYdpi == other.noncompatYdpi;
+ }
+
+ @Override
+ public int hashCode() {
+ return widthPixels * heightPixels * densityDpi;
}
@Override
diff --git a/core/java/android/util/FloatMath.java b/core/java/android/util/FloatMath.java
index 6216638..9556223 100644
--- a/core/java/android/util/FloatMath.java
+++ b/core/java/android/util/FloatMath.java
@@ -71,4 +71,33 @@ public class FloatMath {
* @return the square root of value
*/
public static native float sqrt(float value);
+
+ /**
+ * Returns the closest float approximation of the raising "e" to the power
+ * of the argument.
+ *
+ * @param value to compute the exponential of
+ * @return the exponential of value
+ */
+ public static native float exp(float value);
+
+ /**
+ * Returns the closest float approximation of the result of raising {@code
+ * x} to the power of {@code y}.
+ *
+ * @param x the base of the operation.
+ * @param y the exponent of the operation.
+ * @return {@code x} to the power of {@code y}.
+ */
+ public static native float pow(float x, float y);
+
+ /**
+ * Returns {@code sqrt(}<i>{@code x}</i><sup>{@code 2}</sup>{@code +} <i>
+ * {@code y}</i><sup>{@code 2}</sup>{@code )}.
+ *
+ * @param x a float number
+ * @param y a float number
+ * @return the hypotenuse
+ */
+ public static native float hypot(float x, float y);
}
diff --git a/core/java/android/util/LocaleUtil.java b/core/java/android/util/LocaleUtil.java
deleted file mode 100644
index 93f5cd3..0000000
--- a/core/java/android/util/LocaleUtil.java
+++ /dev/null
@@ -1,85 +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.util;
-
-import java.util.Locale;
-
-import android.view.View;
-import libcore.icu.ICU;
-
-/**
- * Various utilities for Locales
- *
- * @hide
- */
-public class LocaleUtil {
-
- private LocaleUtil() { /* cannot be instantiated */ }
-
- private static String ARAB_SCRIPT_SUBTAG = "Arab";
- private static String HEBR_SCRIPT_SUBTAG = "Hebr";
-
- /**
- * Return the layout direction for a given Locale
- *
- * @param locale the Locale for which we want the layout direction. Can be null.
- * @return the layout direction. This may be one of:
- * {@link View#LAYOUT_DIRECTION_LTR} or
- * {@link View#LAYOUT_DIRECTION_RTL}.
- *
- * Warning: this code does not support vertical scripts.
- * @hide
- */
- public static int getLayoutDirectionFromLocale(Locale locale) {
- if (locale != null && !locale.equals(Locale.ROOT)) {
- final String scriptSubtag = ICU.getScript(ICU.addLikelySubtags(locale.toString()));
- if (scriptSubtag == null) return getLayoutDirectionFromFirstChar(locale);
-
- if (scriptSubtag.equalsIgnoreCase(ARAB_SCRIPT_SUBTAG) ||
- scriptSubtag.equalsIgnoreCase(HEBR_SCRIPT_SUBTAG)) {
- return View.LAYOUT_DIRECTION_RTL;
- }
- }
-
- return View.LAYOUT_DIRECTION_LTR;
- }
-
- /**
- * Fallback algorithm to detect the locale direction. Rely on the fist char of the
- * localized locale name. This will not work if the localized locale name is in English
- * (this is the case for ICU 4.4 and "Urdu" script)
- *
- * @param locale
- * @return the layout direction. This may be one of:
- * {@link View#LAYOUT_DIRECTION_LTR} or
- * {@link View#LAYOUT_DIRECTION_RTL}.
- *
- * Warning: this code does not support vertical scripts.
- * @hide
- */
- private static int getLayoutDirectionFromFirstChar(Locale locale) {
- switch(Character.getDirectionality(locale.getDisplayName(locale).charAt(0))) {
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
- return View.LAYOUT_DIRECTION_RTL;
-
- case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
- default:
- return View.LAYOUT_DIRECTION_LTR;
- }
- }
-}
diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java
index 51e373c..dd504c1 100644
--- a/core/java/android/util/LruCache.java
+++ b/core/java/android/util/LruCache.java
@@ -186,10 +186,13 @@ public class LruCache<K, V> {
}
/**
+ * Remove the eldest entries until the total of remaining entries is at or
+ * below the requested size.
+ *
* @param maxSize the maximum size of the cache before returning. May be -1
- * to evict even 0-sized elements.
+ * to evict even 0-sized elements.
*/
- private void trimToSize(int maxSize) {
+ public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 2179ff3..602a68c 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -59,10 +59,10 @@ public class NtpTrustedTime implements TrustedTime {
final long defaultTimeout = res.getInteger(
com.android.internal.R.integer.config_ntpTimeout);
- final String secureServer = Settings.Secure.getString(
- resolver, Settings.Secure.NTP_SERVER);
- final long timeout = Settings.Secure.getLong(
- resolver, Settings.Secure.NTP_TIMEOUT, defaultTimeout);
+ final String secureServer = Settings.Global.getString(
+ resolver, Settings.Global.NTP_SERVER);
+ final long timeout = Settings.Global.getLong(
+ resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout);
final String server = secureServer != null ? secureServer : defaultServer;
sSingleton = new NtpTrustedTime(server, timeout);
@@ -71,7 +71,7 @@ public class NtpTrustedTime implements TrustedTime {
return sSingleton;
}
- /** {@inheritDoc} */
+ @Override
public boolean forceRefresh() {
if (mServer == null) {
// missing server, so no trusted time available
@@ -91,12 +91,12 @@ public class NtpTrustedTime implements TrustedTime {
}
}
- /** {@inheritDoc} */
+ @Override
public boolean hasCache() {
return mHasCache;
}
- /** {@inheritDoc} */
+ @Override
public long getCacheAge() {
if (mHasCache) {
return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;
@@ -105,7 +105,7 @@ public class NtpTrustedTime implements TrustedTime {
}
}
- /** {@inheritDoc} */
+ @Override
public long getCacheCertainty() {
if (mHasCache) {
return mCachedNtpCertainty;
@@ -114,7 +114,7 @@ public class NtpTrustedTime implements TrustedTime {
}
}
- /** {@inheritDoc} */
+ @Override
public long currentTimeMillis() {
if (!mHasCache) {
throw new IllegalStateException("Missing authoritative time source");
diff --git a/core/java/android/util/Pair.java b/core/java/android/util/Pair.java
index bf25306..6027d08 100644
--- a/core/java/android/util/Pair.java
+++ b/core/java/android/util/Pair.java
@@ -16,6 +16,8 @@
package android.util;
+import libcore.util.Objects;
+
/**
* Container to ease passing around a tuple of two objects. This object provides a sensible
* implementation of equals(), returning true if equals() is true on each of the contained
@@ -26,8 +28,8 @@ public class Pair<F, S> {
public final S second;
/**
- * Constructor for a Pair. If either are null then equals() and hashCode() will throw
- * a NullPointerException.
+ * Constructor for a Pair.
+ *
* @param first the first object in the Pair
* @param second the second object in the pair
*/
@@ -37,31 +39,30 @@ public class Pair<F, S> {
}
/**
- * Checks the two objects for equality by delegating to their respective equals() methods.
- * @param o the Pair to which this one is to be checked for equality
- * @return true if the underlying objects of the Pair are both considered equals()
+ * Checks the two objects for equality by delegating to their respective
+ * {@link Object#equals(Object)} methods.
+ *
+ * @param o the {@link Pair} to which this one is to be checked for equality
+ * @return true if the underlying objects of the Pair are both considered
+ * equal
*/
+ @Override
public boolean equals(Object o) {
- if (o == this) return true;
- if (!(o instanceof Pair)) return false;
- final Pair<F, S> other;
- try {
- other = (Pair<F, S>) o;
- } catch (ClassCastException e) {
+ if (!(o instanceof Pair)) {
return false;
}
- return first.equals(other.first) && second.equals(other.second);
+ Pair<?, ?> p = (Pair<?, ?>) o;
+ return Objects.equal(p.first, first) && Objects.equal(p.second, second);
}
/**
* Compute a hash code using the hash codes of the underlying objects
+ *
* @return a hashcode of the Pair
*/
+ @Override
public int hashCode() {
- int result = 17;
- result = 31 * result + first.hashCode();
- result = 31 * result + second.hashCode();
- return result;
+ return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
}
/**
diff --git a/core/java/android/util/Spline.java b/core/java/android/util/Spline.java
new file mode 100644
index 0000000..ed027eb
--- /dev/null
+++ b/core/java/android/util/Spline.java
@@ -0,0 +1,156 @@
+/*
+ * 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.util;
+
+/**
+ * Performs spline interpolation given a set of control points.
+ * @hide
+ */
+public final class Spline {
+ private final float[] mX;
+ private final float[] mY;
+ private final float[] mM;
+
+ private Spline(float[] x, float[] y, float[] m) {
+ mX = x;
+ mY = y;
+ mM = m;
+ }
+
+ /**
+ * Creates a monotone cubic spline from a given set of control points.
+ *
+ * The spline is guaranteed to pass through each control point exactly.
+ * Moreover, assuming the control points are monotonic (Y is non-decreasing or
+ * non-increasing) then the interpolated values will also be monotonic.
+ *
+ * This function uses the Fritsch-Carlson method for computing the spline parameters.
+ * http://en.wikipedia.org/wiki/Monotone_cubic_interpolation
+ *
+ * @param x The X component of the control points, strictly increasing.
+ * @param y The Y component of the control points, monotonic.
+ * @return
+ *
+ * @throws IllegalArgumentException if the X or Y arrays are null, have
+ * different lengths or have fewer than 2 values.
+ * @throws IllegalArgumentException if the control points are not monotonic.
+ */
+ public static Spline createMonotoneCubicSpline(float[] x, float[] y) {
+ if (x == null || y == null || x.length != y.length || x.length < 2) {
+ throw new IllegalArgumentException("There must be at least two control "
+ + "points and the arrays must be of equal length.");
+ }
+
+ final int n = x.length;
+ float[] d = new float[n - 1]; // could optimize this out
+ float[] m = new float[n];
+
+ // Compute slopes of secant lines between successive points.
+ for (int i = 0; i < n - 1; i++) {
+ float h = x[i + 1] - x[i];
+ if (h <= 0f) {
+ throw new IllegalArgumentException("The control points must all "
+ + "have strictly increasing X values.");
+ }
+ d[i] = (y[i + 1] - y[i]) / h;
+ }
+
+ // Initialize the tangents as the average of the secants.
+ m[0] = d[0];
+ for (int i = 1; i < n - 1; i++) {
+ m[i] = (d[i - 1] + d[i]) * 0.5f;
+ }
+ m[n - 1] = d[n - 2];
+
+ // Update the tangents to preserve monotonicity.
+ for (int i = 0; i < n - 1; i++) {
+ if (d[i] == 0f) { // successive Y values are equal
+ m[i] = 0f;
+ m[i + 1] = 0f;
+ } else {
+ float a = m[i] / d[i];
+ float b = m[i + 1] / d[i];
+ if (a < 0f || b < 0f) {
+ throw new IllegalArgumentException("The control points must have "
+ + "monotonic Y values.");
+ }
+ float h = FloatMath.hypot(a, b);
+ if (h > 9f) {
+ float t = 3f / h;
+ m[i] = t * a * d[i];
+ m[i + 1] = t * b * d[i];
+ }
+ }
+ }
+ return new Spline(x, y, m);
+ }
+
+ /**
+ * Interpolates the value of Y = f(X) for given X.
+ * Clamps X to the domain of the spline.
+ *
+ * @param x The X value.
+ * @return The interpolated Y = f(X) value.
+ */
+ public float interpolate(float x) {
+ // Handle the boundary cases.
+ final int n = mX.length;
+ if (Float.isNaN(x)) {
+ return x;
+ }
+ if (x <= mX[0]) {
+ return mY[0];
+ }
+ if (x >= mX[n - 1]) {
+ return mY[n - 1];
+ }
+
+ // Find the index 'i' of the last point with smaller X.
+ // We know this will be within the spline due to the boundary tests.
+ int i = 0;
+ while (x >= mX[i + 1]) {
+ i += 1;
+ if (x == mX[i]) {
+ return mY[i];
+ }
+ }
+
+ // Perform cubic Hermite spline interpolation.
+ float h = mX[i + 1] - mX[i];
+ float t = (x - mX[i]) / h;
+ return (mY[i] * (1 + 2 * t) + h * mM[i] * t) * (1 - t) * (1 - t)
+ + (mY[i + 1] * (3 - 2 * t) + h * mM[i + 1] * (t - 1)) * t * t;
+ }
+
+ // For debugging.
+ @Override
+ public String toString() {
+ StringBuilder str = new StringBuilder();
+ final int n = mX.length;
+ str.append("[");
+ for (int i = 0; i < n; i++) {
+ if (i != 0) {
+ str.append(", ");
+ }
+ str.append("(").append(mX[i]);
+ str.append(", ").append(mY[i]);
+ str.append(": ").append(mM[i]).append(")");
+ }
+ str.append("]");
+ return str.toString();
+ }
+}
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index c4ebec4..5a4f322 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -18,8 +18,11 @@ package android.util;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+
+import com.android.internal.util.XmlUtils;
-import libcore.util.ZoneInfoDB;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -28,10 +31,10 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
-import java.util.TimeZone;
import java.util.Date;
+import java.util.TimeZone;
-import com.android.internal.util.XmlUtils;
+import libcore.util.ZoneInfoDB;
/**
* A class containing utility methods related to time zones.
@@ -245,6 +248,8 @@ public class TimeUtils {
private static final Object sFormatSync = new Object();
private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+5];
+ private static final long LARGEST_DURATION = (1000 * DateUtils.DAY_IN_MILLIS) - 1;
+
static private int accumField(int amt, int suffix, boolean always, int zeropad) {
if (amt > 99 || (always && zeropad >= 3)) {
return 3+suffix;
@@ -307,6 +312,10 @@ public class TimeUtils {
duration = -duration;
}
+ if (duration > LARGEST_DURATION) {
+ duration = LARGEST_DURATION;
+ }
+
int millis = (int)(duration%1000);
int seconds = (int) Math.floor(duration / 1000);
int days = 0, hours = 0, minutes = 0;
@@ -383,6 +392,18 @@ public class TimeUtils {
formatDuration(time-now, pw, 0);
}
+ /** @hide Just for debugging; not internationalized. */
+ public static String formatUptime(long time) {
+ final long diff = time - SystemClock.uptimeMillis();
+ if (diff > 0) {
+ return time + " (in " + diff + " ms)";
+ }
+ if (diff < 0) {
+ return time + " (" + -diff + " ms ago)";
+ }
+ return time + " (now)";
+ }
+
/**
* Convert a System.currentTimeMillis() value to a time of day value like
* that printed in logs. MM-DD HH:MM:SS.MMM
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 0aabc44..9bee4bf 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -18,22 +18,21 @@ package android.view;
import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
+import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
-import android.util.Pool;
-import android.util.Poolable;
-import android.util.PoolableManager;
-import android.util.Pools;
import android.util.SparseLongArray;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+import com.android.internal.os.SomeArgs;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -47,7 +46,6 @@ import java.util.Map;
* UI thread.
*/
final class AccessibilityInteractionController {
- private static final int POOL_SIZE = 5;
private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
new ArrayList<AccessibilityNodeInfo>();
@@ -64,6 +62,8 @@ final class AccessibilityInteractionController {
private final ArrayList<View> mTempArrayList = new ArrayList<View>();
+ private final Rect mTempRect = new Rect();
+
public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
Looper looper = viewRootImpl.mHandler.getLooper();
mMyLooperThreadId = looper.getThread().getId();
@@ -73,60 +73,6 @@ final class AccessibilityInteractionController {
mPrefetcher = new AccessibilityNodePrefetcher();
}
- // Reusable poolable arguments for interacting with the view hierarchy
- // to fit more arguments than Message and to avoid sharing objects between
- // two messages since several threads can send messages concurrently.
- private final Pool<SomeArgs> mPool = Pools.synchronizedPool(Pools.finitePool(
- new PoolableManager<SomeArgs>() {
- public SomeArgs newInstance() {
- return new SomeArgs();
- }
-
- public void onAcquired(SomeArgs info) {
- /* do nothing */
- }
-
- public void onReleased(SomeArgs info) {
- info.clear();
- }
- }, POOL_SIZE)
- );
-
- private class SomeArgs implements Poolable<SomeArgs> {
- private SomeArgs mNext;
- private boolean mIsPooled;
-
- public Object arg1;
- public Object arg2;
- public int argi1;
- public int argi2;
- public int argi3;
-
- public SomeArgs getNextPoolable() {
- return mNext;
- }
-
- public boolean isPooled() {
- return mIsPooled;
- }
-
- public void setNextPoolable(SomeArgs args) {
- mNext = args;
- }
-
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
-
- private void clear() {
- arg1 = null;
- arg2 = null;
- argi1 = 0;
- argi2 = 0;
- argi3 = 0;
- }
- }
-
private boolean isShown(View view) {
// The first two checks are made also made by isShown() which
// however traverses the tree up to the parent to catch that.
@@ -138,24 +84,18 @@ final class AccessibilityInteractionController {
}
public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
- long accessibilityNodeId, int windowLeft, int windowTop, int interactionId,
+ long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
long interrogatingTid) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
message.arg1 = flags;
- SomeArgs args = mPool.acquire();
+ SomeArgs args = SomeArgs.obtain();
args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
args.argi3 = interactionId;
args.arg1 = callback;
-
- SomeArgs moreArgs = mPool.acquire();
- moreArgs.argi1 = windowLeft;
- moreArgs.argi2 = windowTop;
- args.arg2 = moreArgs;
-
message.obj = args;
// If the interrogation is performed by the same thread as the main UI
@@ -180,12 +120,7 @@ final class AccessibilityInteractionController {
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
- SomeArgs moreArgs = (SomeArgs) args.arg2;
- mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
- mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
-
- mPool.release(moreArgs);
- mPool.release(args);
+ args.recycle();
List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
infos.clear();
@@ -207,6 +142,7 @@ final class AccessibilityInteractionController {
} finally {
try {
mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ applyApplicationScaleIfNeeded(infos);
callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
infos.clear();
} catch (RemoteException re) {
@@ -216,24 +152,18 @@ final class AccessibilityInteractionController {
}
public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
- int viewId, int windowLeft, int windowTop, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid) {
+ int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ int flags, int interrogatingPid, long interrogatingTid) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
message.arg1 = flags;
message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
- SomeArgs args = mPool.acquire();
+ SomeArgs args = SomeArgs.obtain();
args.argi1 = viewId;
args.argi2 = interactionId;
args.arg1 = callback;
- SomeArgs moreArgs = mPool.acquire();
- moreArgs.argi1 = windowLeft;
- moreArgs.argi2 = windowTop;
- args.arg2 = moreArgs;
-
message.obj = args;
// If the interrogation is performed by the same thread as the main UI
@@ -258,12 +188,7 @@ final class AccessibilityInteractionController {
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
- SomeArgs moreArgs = (SomeArgs) args.arg2;
- mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
- mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
-
- mPool.release(moreArgs);
- mPool.release(args);
+ args.recycle();
AccessibilityNodeInfo info = null;
try {
@@ -287,6 +212,7 @@ final class AccessibilityInteractionController {
} finally {
try {
mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ applyApplicationScaleIfNeeded(info);
callback.setFindAccessibilityNodeInfoResult(info, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -295,25 +221,19 @@ final class AccessibilityInteractionController {
}
public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
- String text, int windowLeft, int windowTop, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid) {
+ String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ int flags, int interrogatingPid, long interrogatingTid) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
message.arg1 = flags;
- SomeArgs args = mPool.acquire();
+ SomeArgs args = SomeArgs.obtain();
args.arg1 = text;
+ args.arg2 = callback;
args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
args.argi3 = interactionId;
- SomeArgs moreArgs = mPool.acquire();
- moreArgs.arg1 = callback;
- moreArgs.argi1 = windowLeft;
- moreArgs.argi2 = windowTop;
- args.arg2 = moreArgs;
-
message.obj = args;
// If the interrogation is performed by the same thread as the main UI
@@ -333,18 +253,12 @@ final class AccessibilityInteractionController {
SomeArgs args = (SomeArgs) message.obj;
final String text = (String) args.arg1;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg2;
final int accessibilityViewId = args.argi1;
final int virtualDescendantId = args.argi2;
final int interactionId = args.argi3;
-
- SomeArgs moreArgs = (SomeArgs) args.arg2;
- final IAccessibilityInteractionConnectionCallback callback =
- (IAccessibilityInteractionConnectionCallback) moreArgs.arg1;
- mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
- mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
-
- mPool.release(moreArgs);
- mPool.release(args);
+ args.recycle();
List<AccessibilityNodeInfo> infos = null;
try {
@@ -396,6 +310,7 @@ final class AccessibilityInteractionController {
} finally {
try {
mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ applyApplicationScaleIfNeeded(infos);
callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -403,25 +318,20 @@ final class AccessibilityInteractionController {
}
}
- public void findFocusClientThread(long accessibilityNodeId, int focusType, int windowLeft,
- int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int flags, int interogatingPid, long interrogatingTid) {
+ public void findFocusClientThread(long accessibilityNodeId, int focusType, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
+ long interrogatingTid) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FIND_FOCUS;
message.arg1 = flags;
message.arg2 = focusType;
- SomeArgs args = mPool.acquire();
+ SomeArgs args = SomeArgs.obtain();
args.argi1 = interactionId;
args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
args.arg1 = callback;
- SomeArgs moreArgs = mPool.acquire();
- moreArgs.argi1 = windowLeft;
- moreArgs.argi2 = windowTop;
- args.arg2 = moreArgs;
-
message.obj = args;
// If the interrogation is performed by the same thread as the main UI
@@ -447,12 +357,7 @@ final class AccessibilityInteractionController {
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
- SomeArgs moreArgs = (SomeArgs) args.arg2;
- mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
- mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
-
- mPool.release(moreArgs);
- mPool.release(args);
+ args.recycle();
AccessibilityNodeInfo focused = null;
try {
@@ -502,6 +407,7 @@ final class AccessibilityInteractionController {
} finally {
try {
mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ applyApplicationScaleIfNeeded(focused);
callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -509,25 +415,19 @@ final class AccessibilityInteractionController {
}
}
- public void focusSearchClientThread(long accessibilityNodeId, int direction, int windowLeft,
- int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int flags, int interogatingPid, long interrogatingTid) {
+ public void focusSearchClientThread(long accessibilityNodeId, int direction, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
+ long interrogatingTid) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FOCUS_SEARCH;
message.arg1 = flags;
message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
- SomeArgs args = mPool.acquire();
- args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ SomeArgs args = SomeArgs.obtain();
args.argi2 = direction;
args.argi3 = interactionId;
args.arg1 = callback;
- SomeArgs moreArgs = mPool.acquire();
- moreArgs.argi1 = windowLeft;
- moreArgs.argi2 = windowTop;
- args.arg2 = moreArgs;
-
message.obj = args;
// If the interrogation is performed by the same thread as the main UI
@@ -547,18 +447,12 @@ final class AccessibilityInteractionController {
final int accessibilityViewId = message.arg2;
SomeArgs args = (SomeArgs) message.obj;
- final int virtualDescendantId = args.argi1;
final int direction = args.argi2;
final int interactionId = args.argi3;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
- SomeArgs moreArgs = (SomeArgs) args.arg2;
- mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
- mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
-
- mPool.release(moreArgs);
- mPool.release(args);
+ args.recycle();
AccessibilityNodeInfo next = null;
try {
@@ -574,43 +468,15 @@ final class AccessibilityInteractionController {
root = mViewRootImpl.mView;
}
if (root != null && isShown(root)) {
- if ((direction & View.FOCUS_ACCESSIBILITY) == View.FOCUS_ACCESSIBILITY) {
- AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
- if (provider != null) {
- next = provider.accessibilityFocusSearch(direction, virtualDescendantId);
- if (next != null) {
- return;
- }
- }
- View nextView = root.focusSearch(direction);
- while (nextView != null) {
- // If the focus search reached a node with a provider
- // we delegate to the provider to find the next one.
- // If the provider does not return a virtual view to
- // take accessibility focus we try the next view found
- // by the focus search algorithm.
- provider = nextView.getAccessibilityNodeProvider();
- if (provider != null) {
- next = provider.accessibilityFocusSearch(direction, View.NO_ID);
- if (next != null) {
- break;
- }
- nextView = nextView.focusSearch(direction);
- } else {
- next = nextView.createAccessibilityNodeInfo();
- break;
- }
- }
- } else {
- View nextView = root.focusSearch(direction);
- if (nextView != null) {
- next = nextView.createAccessibilityNodeInfo();
- }
+ View nextView = root.focusSearch(direction);
+ if (nextView != null) {
+ next = nextView.createAccessibilityNodeInfo();
}
}
} finally {
try {
mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ applyApplicationScaleIfNeeded(next);
callback.setFindAccessibilityNodeInfoResult(next, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -627,7 +493,7 @@ final class AccessibilityInteractionController {
message.arg1 = flags;
message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
- SomeArgs args = mPool.acquire();
+ SomeArgs args = SomeArgs.obtain();
args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
args.argi2 = action;
args.argi3 = interactionId;
@@ -660,7 +526,7 @@ final class AccessibilityInteractionController {
(IAccessibilityInteractionConnectionCallback) args.arg1;
Bundle arguments = (Bundle) args.arg2;
- mPool.release(args);
+ args.recycle();
boolean succeeded = false;
try {
@@ -706,6 +572,39 @@ final class AccessibilityInteractionController {
return foundView;
}
+ private void applyApplicationScaleIfNeeded(List<AccessibilityNodeInfo> infos) {
+ if (infos == null) {
+ return;
+ }
+ final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
+ if (applicationScale != 1.0f) {
+ final int infoCount = infos.size();
+ for (int i = 0; i < infoCount; i++) {
+ AccessibilityNodeInfo info = infos.get(i);
+ applyApplicationScaleIfNeeded(info);
+ }
+ }
+ }
+
+ private void applyApplicationScaleIfNeeded(AccessibilityNodeInfo info) {
+ if (info == null) {
+ return;
+ }
+ final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
+ if (applicationScale != 1.0f) {
+ Rect bounds = mTempRect;
+
+ info.getBoundsInParent(bounds);
+ bounds.scale(applicationScale);
+ info.setBoundsInParent(bounds);
+
+ info.getBoundsInScreen(bounds);
+ bounds.scale(applicationScale);
+ info.setBoundsInScreen(bounds);
+ }
+ }
+
+
/**
* This class encapsulates a prefetching strategy for the accessibility APIs for
* querying window content. It is responsible to prefetch a batch of
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 78dc86f..b661748 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -16,6 +16,7 @@
package android.view;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -164,8 +165,8 @@ public final class Choreographer {
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
mLastFrameTimeNanos = Long.MIN_VALUE;
- mFrameIntervalNanos = (long)(1000000000 /
- new Display(Display.DEFAULT_DISPLAY, null).getRefreshRate());
+
+ mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
@@ -173,6 +174,12 @@ public final class Choreographer {
}
}
+ private static float getRefreshRate() {
+ DisplayInfo di = DisplayManagerGlobal.getInstance().getDisplayInfo(
+ Display.DEFAULT_DISPLAY);
+ return di.refreshRate;
+ }
+
/**
* Gets the choreographer for the calling thread. Must be called from
* a thread that already has a {@link android.os.Looper} associated with it.
@@ -677,7 +684,24 @@ public final class Choreographer {
}
@Override
- public void onVsync(long timestampNanos, int frame) {
+ public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
+ // Ignore vsync from secondary display.
+ // This can be problematic because the call to scheduleVsync() is a one-shot.
+ // We need to ensure that we will still receive the vsync from the primary
+ // display which is the one we really care about. Ideally we should schedule
+ // vsync for a particular display.
+ // At this time Surface Flinger won't send us vsyncs for secondary displays
+ // but that could change in the future so let's log a message to help us remember
+ // that we need to fix this.
+ if (builtInDisplayId != Surface.BUILT_IN_DISPLAY_ID_MAIN) {
+ Log.d(TAG, "Received vsync from secondary display, but we don't support "
+ + "this case yet. Choreographer needs a way to explicitly request "
+ + "vsync for a specific display to ensure it doesn't lose track "
+ + "of its scheduled vsync.");
+ scheduleVsync();
+ return;
+ }
+
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java
index 626f385..6c733f9 100644
--- a/core/java/android/view/ContextThemeWrapper.java
+++ b/core/java/android/view/ContextThemeWrapper.java
@@ -18,6 +18,7 @@ package android.view;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
@@ -30,6 +31,8 @@ public class ContextThemeWrapper extends ContextWrapper {
private int mThemeResource;
private Resources.Theme mTheme;
private LayoutInflater mInflater;
+ private Configuration mOverrideConfiguration;
+ private Resources mResources;
public ContextThemeWrapper() {
super(null);
@@ -45,6 +48,41 @@ public class ContextThemeWrapper extends ContextWrapper {
super.attachBaseContext(newBase);
mBase = newBase;
}
+
+ /**
+ * Call to set an "override configuration" on this context -- this is
+ * a configuration that replies one or more values of the standard
+ * configuration that is applied to the context. See
+ * {@link Context#createConfigurationContext(Configuration)} for more
+ * information.
+ *
+ * <p>This method can only be called once, and must be called before any
+ * calls to {@link #getResources()} are made.
+ */
+ public void applyOverrideConfiguration(Configuration overrideConfiguration) {
+ if (mResources != null) {
+ throw new IllegalStateException("getResources() has already been called");
+ }
+ if (mOverrideConfiguration != null) {
+ throw new IllegalStateException("Override configuration has already been set");
+ }
+ mOverrideConfiguration = new Configuration(overrideConfiguration);
+ }
+
+ @Override
+ public Resources getResources() {
+ if (mResources != null) {
+ return mResources;
+ }
+ if (mOverrideConfiguration == null) {
+ mResources = super.getResources();
+ return mResources;
+ } else {
+ Context resc = createConfigurationContext(mOverrideConfiguration);
+ mResources = resc.getResources();
+ return mResources;
+ }
+ }
@Override public void setTheme(int resid) {
mThemeResource = resid;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index c947312..cf58458 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -16,61 +16,213 @@
package android.view;
-import android.content.res.CompatibilityInfo;
+import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.SystemClock;
import android.util.DisplayMetrics;
-import android.util.Slog;
+import android.util.Log;
/**
- * Provides information about the display size and density.
+ * Provides information about the size and density of a logical display.
+ * <p>
+ * The display area is described in two different ways.
+ * <ul>
+ * <li>The application display area specifies the part of the display that may contain
+ * an application window, excluding the system decorations. The application display area may
+ * be smaller than the real display area because the system subtracts the space needed
+ * for decor elements such as the status bar. Use the following methods to query the
+ * application display area: {@link #getSize}, {@link #getRectSize} and {@link #getMetrics}.</li>
+ * <li>The real display area specifies the part of the display that contains content
+ * including the system decorations. Even so, the real display area may be smaller than the
+ * physical size of the display if the window manager is emulating a smaller display
+ * using (adb shell am display-size). Use the following methods to query the
+ * real display area: {@link #getRealSize}, {@link #getRealMetrics}.</li>
+ * </ul>
+ * </p><p>
+ * A logical display does not necessarily represent a particular physical display device
+ * such as the built-in screen or an external monitor. The contents of a logical
+ * display may be presented on one or more physical displays according to the devices
+ * that are currently attached and whether mirroring has been enabled.
+ * </p>
*/
-public class Display {
- static final String TAG = "Display";
- static final boolean DEBUG_DISPLAY_SIZE = false;
+public final class Display {
+ private static final String TAG = "Display";
+ private static final boolean DEBUG = false;
+
+ private final DisplayManagerGlobal mGlobal;
+ private final int mDisplayId;
+ private final int mLayerStack;
+ private final String mName;
+ private final CompatibilityInfoHolder mCompatibilityInfo;
+
+ private DisplayInfo mDisplayInfo; // never null
+ private boolean mIsValid;
+
+ // Temporary display metrics structure used for compatibility mode.
+ private final DisplayMetrics mTempMetrics = new DisplayMetrics();
+
+ // We cache the app width and height properties briefly between calls
+ // to getHeight() and getWidth() to ensure that applications perceive
+ // consistent results when the size changes (most of the time).
+ // Applications should now be using getSize() instead.
+ private static final int CACHED_APP_SIZE_DURATION_MILLIS = 20;
+ private long mLastCachedAppSizeUpdate;
+ private int mCachedAppWidthCompat;
+ private int mCachedAppHeightCompat;
/**
- * The default Display id.
+ * The default Display id, which is the id of the built-in primary display
+ * assuming there is one.
*/
public static final int DEFAULT_DISPLAY = 0;
/**
- * Use {@link android.view.WindowManager#getDefaultDisplay()
- * WindowManager.getDefaultDisplay()} to create a Display object.
- * Display gives you access to some information about a particular display
- * connected to the device.
+ * Display flag: Indicates that the display supports secure video output.
+ * <p>
+ * This flag is used to indicate that the display supports content protection
+ * mechanisms for secure video output at the display interface, such as HDCP.
+ * These mechanisms may be used to protect secure content as it leaves the device.
+ * </p><p>
+ * While mirroring content to multiple displays, it can happen that certain
+ * display devices support secure video output while other display devices do not.
+ * The secure content will be shown only on the display devices that support
+ * secure video output and will be blanked on other display devices that do
+ * not support secure video output.
+ * </p><p>
+ * This flag mainly applies to external display devices such as HDMI or
+ * Wifi display. Built-in display devices are usually considered secure.
+ * </p>
+ *
+ * @hide pending review
*/
- Display(int display, CompatibilityInfoHolder compatInfo) {
- // initalize the statics when this class is first instansiated. This is
- // done here instead of in the static block because Zygote
- synchronized (sStaticInit) {
- if (!sInitialized) {
- nativeClassInit();
- sInitialized = true;
- }
- }
- mCompatibilityInfo = compatInfo != null ? compatInfo : new CompatibilityInfoHolder();
- mDisplay = display;
- init(display);
+ public static final int FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT = 1 << 0;
+
+ /**
+ * Display flag: Indicates that the display supports secure in-memory video buffers.
+ * <p>
+ * This flag is used to indicate that the display supports content protection
+ * mechanisms for in-memory video buffers, such as secure memory areas.
+ * These mechanisms may be used to protect secure video buffers in memory from
+ * the video decoder to the display compositor and the video interface.
+ * </p>
+ *
+ * @hide pending review
+ */
+ public static final int FLAG_SUPPORTS_SECURE_VIDEO_BUFFERS = 1 << 1;
+
+ /**
+ * Internal method to create a display.
+ * Applications should use {@link android.view.WindowManager#getDefaultDisplay()}
+ * or {@link android.hardware.display.DisplayManager#getDisplay}
+ * to get a display object.
+ *
+ * @hide
+ */
+ public Display(DisplayManagerGlobal global,
+ int displayId, DisplayInfo displayInfo /*not null*/,
+ CompatibilityInfoHolder compatibilityInfo) {
+ mGlobal = global;
+ mDisplayId = displayId;
+ mDisplayInfo = displayInfo;
+ mLayerStack = displayInfo.layerStack; // can never change as long as the display is valid
+ mName = displayInfo.name; // cannot change as long as the display is valid
+ mCompatibilityInfo = compatibilityInfo;
+ mIsValid = true;
}
/**
- * Returns the index of this display. This is currently undefined; do
- * not use.
+ * Gets the display id.
+ * <p>
+ * Each logical display has a unique id.
+ * The default display has id {@link #DEFAULT_DISPLAY}.
+ * </p>
*/
public int getDisplayId() {
- return mDisplay;
+ return mDisplayId;
+ }
+
+ /**
+ * Returns true if this display is still valid, false if the display has been removed.
+ *
+ * If the display is invalid, then the methods of this class will
+ * continue to report the most recently observed display information.
+ * However, it is unwise (and rather fruitless) to continue using a
+ * {@link Display} object after the display's demise.
+ *
+ * It's possible for a display that was previously invalid to become
+ * valid again if a display with the same id is reconnected.
+ *
+ * @return True if the display is still valid.
+ */
+ public boolean isValid() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mIsValid;
+ }
}
/**
- * Returns the number of displays connected to the device. This is
- * currently undefined; do not use.
+ * Gets a full copy of the display information.
+ *
+ * @param outDisplayInfo The object to receive the copy of the display information.
+ * @return True if the display is still valid.
+ * @hide
*/
- native static int getDisplayCount();
-
+ public boolean getDisplayInfo(DisplayInfo outDisplayInfo) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ outDisplayInfo.copyFrom(mDisplayInfo);
+ return mIsValid;
+ }
+ }
+
+ /**
+ * Gets the display's layer stack.
+ *
+ * Each display has its own independent layer stack upon which surfaces
+ * are placed to be managed by surface flinger.
+ *
+ * @return The display's layer stack number.
+ * @hide
+ */
+ public int getLayerStack() {
+ return mLayerStack;
+ }
+
+ /**
+ * Returns a combination of flags that describe the capabilities of the display.
+ *
+ * @return The display flags.
+ *
+ * @hide pending review
+ */
+ public int getFlags() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.flags;
+ }
+ }
+
+ /**
+ * Gets the compatibility info used by this display instance.
+ *
+ * @return The compatibility info holder, or null if none is required.
+ * @hide
+ */
+ public CompatibilityInfoHolder getCompatibilityInfo() {
+ return mCompatibilityInfo;
+ }
+
+ /**
+ * Gets the name of the display.
+ * @return The display's name.
+ */
+ public String getName() {
+ return mName;
+ }
+
/**
* Gets the size of the display, in pixels.
* <p>
@@ -84,7 +236,7 @@ public class Display {
* </p><p>
* The size returned by this method does not necessarily represent the
* actual raw size (native resolution) of the display. The returned size may
- * be adjusted to exclude certain system decor elements that are always visible.
+ * be adjusted to exclude certain system decoration elements that are always visible.
* It may also be scaled to provide compatibility with older applications that
* were originally designed for smaller displays.
* </p>
@@ -92,43 +244,14 @@ public class Display {
* @param outSize A {@link Point} object to receive the size information.
*/
public void getSize(Point outSize) {
- getSizeInternal(outSize, true);
- }
-
- private void getSizeInternal(Point outSize, boolean doCompat) {
- try {
- IWindowManager wm = getWindowManager();
- if (wm != null) {
- wm.getDisplaySize(outSize);
- CompatibilityInfo ci;
- if (doCompat && (ci=mCompatibilityInfo.getIfNeeded()) != null) {
- synchronized (mTmpMetrics) {
- mTmpMetrics.noncompatWidthPixels = outSize.x;
- mTmpMetrics.noncompatHeightPixels = outSize.y;
- mTmpMetrics.density = mDensity;
- ci.applyToDisplayMetrics(mTmpMetrics);
- outSize.x = mTmpMetrics.widthPixels;
- outSize.y = mTmpMetrics.heightPixels;
- }
- }
- } else {
- // This is just for boot-strapping, initializing the
- // system process before the window manager is up.
- outSize.x = getRawWidth();
- outSize.y = getRawHeight();
- }
- if (false) {
- RuntimeException here = new RuntimeException("here");
- here.fillInStackTrace();
- Slog.v(TAG, "Returning display size: " + outSize, here);
- }
- if (DEBUG_DISPLAY_SIZE && doCompat) Slog.v(
- TAG, "Returning display size: " + outSize);
- } catch (RemoteException e) {
- Slog.w("Display", "Unable to get display size", e);
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
+ outSize.x = mTempMetrics.widthPixels;
+ outSize.y = mTempMetrics.heightPixels;
}
}
-
+
/**
* Gets the size of the display as a rectangle, in pixels.
*
@@ -136,9 +259,10 @@ public class Display {
* @see #getSize(Point)
*/
public void getRectSize(Rect outSize) {
- synchronized (mTmpPoint) {
- getSizeInternal(mTmpPoint, true);
- outSize.set(0, 0, mTmpPoint.x, mTmpPoint.y);
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
+ outSize.set(0, 0, mTempMetrics.widthPixels, mTempMetrics.heightPixels);
}
}
@@ -173,15 +297,12 @@ public class Display {
* for example, screen decorations like the status bar are being hidden.
*/
public void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) {
- try {
- IWindowManager wm = getWindowManager();
- wm.getCurrentSizeRange(outSmallestSize, outLargestSize);
- } catch (RemoteException e) {
- Slog.w("Display", "Unable to get display size range", e);
- outSmallestSize.x = 0;
- outSmallestSize.y = 0;
- outLargestSize.x = 0;
- outLargestSize.y = 0;
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ outSmallestSize.x = mDisplayInfo.smallestNominalAppWidth;
+ outSmallestSize.y = mDisplayInfo.smallestNominalAppHeight;
+ outLargestSize.x = mDisplayInfo.largestNominalAppWidth;
+ outLargestSize.y = mDisplayInfo.largestNominalAppHeight;
}
}
@@ -191,12 +312,9 @@ public class Display {
* @hide
*/
public int getMaximumSizeDimension() {
- try {
- IWindowManager wm = getWindowManager();
- return wm.getMaximumSizeDimension();
- } catch (RemoteException e) {
- Slog.w("Display", "Unable to get display maximum size dimension", e);
- return 0;
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return Math.max(mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
}
}
@@ -205,13 +323,9 @@ public class Display {
*/
@Deprecated
public int getWidth() {
- synchronized (mTmpPoint) {
- long now = SystemClock.uptimeMillis();
- if (now > (mLastGetTime+20)) {
- getSizeInternal(mTmpPoint, true);
- mLastGetTime = now;
- }
- return mTmpPoint.x;
+ synchronized (this) {
+ updateCachedAppSizeIfNeededLocked();
+ return mCachedAppWidthCompat;
}
}
@@ -220,76 +334,13 @@ public class Display {
*/
@Deprecated
public int getHeight() {
- synchronized (mTmpPoint) {
- long now = SystemClock.uptimeMillis();
- if (now > (mLastGetTime+20)) {
- getSizeInternal(mTmpPoint, true);
- mLastGetTime = now;
- }
- return mTmpPoint.y;
- }
- }
-
- /**
- * Gets the real size of the display without subtracting any window decor or
- * applying any compatibility scale factors.
- * <p>
- * The real size may be smaller than the raw size when the window manager
- * is emulating a smaller display (using adb shell am display-size).
- * </p><p>
- * The size is adjusted based on the current rotation of the display.
- * </p>
- * @hide
- */
- public void getRealSize(Point outSize) {
- try {
- IWindowManager wm = getWindowManager();
- if (wm != null) {
- wm.getRealDisplaySize(outSize);
- } else {
- // This is just for boot-strapping, initializing the
- // system process before the window manager is up.
- outSize.x = getRawWidth();
- outSize.y = getRawHeight();
- }
- if (DEBUG_DISPLAY_SIZE) Slog.v(
- TAG, "Returning real display size: " + outSize);
- } catch (RemoteException e) {
- Slog.w("Display", "Unable to get real display size", e);
+ synchronized (this) {
+ updateCachedAppSizeIfNeededLocked();
+ return mCachedAppHeightCompat;
}
}
/**
- * Gets the raw width of the display, in pixels.
- * <p>
- * The size is adjusted based on the current rotation of the display.
- * </p>
- * @hide
- */
- public int getRawWidth() {
- int w = getRawWidthNative();
- if (DEBUG_DISPLAY_SIZE) Slog.v(
- TAG, "Returning raw display width: " + w);
- return w;
- }
- private native int getRawWidthNative();
-
- /**
- * Gets the raw height of the display, in pixels.
- * <p>
- * The size is adjusted based on the current rotation of the display.
- * </p>
- * @hide
- */
- public int getRawHeight() {
- int h = getRawHeightNative();
- if (DEBUG_DISPLAY_SIZE) Slog.v(
- TAG, "Returning raw display height: " + h);
- return h;
- }
- private native int getRawHeightNative();
-
- /**
* Returns the rotation of the screen from its "natural" orientation.
* The returned value may be {@link Surface#ROTATION_0 Surface.ROTATION_0}
* (no rotation), {@link Surface#ROTATION_90 Surface.ROTATION_90},
@@ -307,30 +358,43 @@ public class Display {
* {@link Surface#ROTATION_90 Surface.ROTATION_90}.
*/
public int getRotation() {
- return getOrientation();
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.rotation;
+ }
}
-
+
/**
* @deprecated use {@link #getRotation}
* @return orientation of this display.
*/
- @Deprecated native public int getOrientation();
+ @Deprecated
+ public int getOrientation() {
+ return getRotation();
+ }
/**
- * Return the native pixel format of the display. The returned value
- * may be one of the constants int {@link android.graphics.PixelFormat}.
+ * Gets the pixel format of the display.
+ * @return One of the constants defined in {@link android.graphics.PixelFormat}.
+ *
+ * @deprecated This method is no longer supported.
+ * The result is always {@link PixelFormat#RGBA_8888}.
*/
+ @Deprecated
public int getPixelFormat() {
- return mPixelFormat;
+ return PixelFormat.RGBA_8888;
}
-
+
/**
- * Return the refresh rate of this display in frames per second.
+ * Gets the refresh rate of this display in frames per second.
*/
public float getRefreshRate() {
- return mRefreshRate;
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ return mDisplayInfo.refreshRate;
+ }
}
-
+
/**
* Gets display metrics that describe the size and density of this display.
* <p>
@@ -346,117 +410,93 @@ public class Display {
* @param outMetrics A {@link DisplayMetrics} object to receive the metrics.
*/
public void getMetrics(DisplayMetrics outMetrics) {
- synchronized (mTmpPoint) {
- getSizeInternal(mTmpPoint, false);
- getMetricsWithSize(outMetrics, mTmpPoint.x, mTmpPoint.y);
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ mDisplayInfo.getAppMetrics(outMetrics, mCompatibilityInfo);
}
-
- CompatibilityInfo ci = mCompatibilityInfo.getIfNeeded();
- if (ci != null) {
- ci.applyToDisplayMetrics(outMetrics);
- }
-
- if (DEBUG_DISPLAY_SIZE) Slog.v(TAG, "Returning DisplayMetrics: "
- + outMetrics.widthPixels + "x" + outMetrics.heightPixels
- + " " + outMetrics.density);
}
/**
- * Gets display metrics based on the real size of this display.
- * @hide
+ * Gets the real size of the display without subtracting any window decor or
+ * applying any compatibility scale factors.
+ * <p>
+ * The size is adjusted based on the current rotation of the display.
+ * </p><p>
+ * The real size may be smaller than the physical size of the screen when the
+ * window manager is emulating a smaller display (using adb shell am display-size).
+ * </p>
+ *
+ * @param outSize Set to the real size of the display.
*/
- public void getRealMetrics(DisplayMetrics outMetrics) {
- synchronized (mTmpPoint) {
- getRealSize(mTmpPoint);
- getMetricsWithSize(outMetrics, mTmpPoint.x, mTmpPoint.y);
+ public void getRealSize(Point outSize) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ outSize.x = mDisplayInfo.logicalWidth;
+ outSize.y = mDisplayInfo.logicalHeight;
}
}
/**
- * If the display is mirrored to an external HDMI display, returns the
- * width of that display.
- * @hide
- */
- public int getRawExternalWidth() {
- return 1280;
- }
-
- /**
- * If the display is mirrored to an external HDMI display, returns the
- * height of that display.
- * @hide
- */
- public int getRawExternalHeight() {
- return 720;
- }
-
- /**
- * If the display is mirrored to an external HDMI display, returns the
- * rotation of that display relative to its natural orientation.
- * @hide
- */
- public int getExternalRotation() {
- return Surface.ROTATION_0;
- }
-
- /**
- * Gets display metrics based on an explicit assumed display size.
- * @hide
+ * Gets display metrics based on the real size of this display.
+ * <p>
+ * The size is adjusted based on the current rotation of the display.
+ * </p><p>
+ * The real size may be smaller than the physical size of the screen when the
+ * window manager is emulating a smaller display (using adb shell am display-size).
+ * </p>
+ *
+ * @param outMetrics A {@link DisplayMetrics} object to receive the metrics.
*/
- public void getMetricsWithSize(DisplayMetrics outMetrics,
- int width, int height) {
- outMetrics.densityDpi = (int)((mDensity*DisplayMetrics.DENSITY_DEFAULT)+.5f);
-
- outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width;
- outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height;
-
- outMetrics.density = outMetrics.noncompatDensity = mDensity;
- outMetrics.scaledDensity = outMetrics.noncompatScaledDensity = outMetrics.density;
- outMetrics.xdpi = outMetrics.noncompatXdpi = mDpiX;
- outMetrics.ydpi = outMetrics.noncompatYdpi = mDpiY;
+ public void getRealMetrics(DisplayMetrics outMetrics) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ mDisplayInfo.getLogicalMetrics(outMetrics, null);
+ }
}
- static IWindowManager getWindowManager() {
- synchronized (sStaticInit) {
- if (sWindowManager == null) {
- sWindowManager = IWindowManager.Stub.asInterface(
- ServiceManager.getService("window"));
+ private void updateDisplayInfoLocked() {
+ // Note: The display manager caches display info objects on our behalf.
+ DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);
+ if (newInfo == null) {
+ // Preserve the old mDisplayInfo after the display is removed.
+ if (mIsValid) {
+ mIsValid = false;
+ if (DEBUG) {
+ Log.d(TAG, "Logical display " + mDisplayId + " was removed.");
+ }
+ }
+ } else {
+ // Use the new display info. (It might be the same object if nothing changed.)
+ mDisplayInfo = newInfo;
+ if (!mIsValid) {
+ mIsValid = true;
+ if (DEBUG) {
+ Log.d(TAG, "Logical display " + mDisplayId + " was recreated.");
+ }
}
- return sWindowManager;
}
}
- /*
- * We use a class initializer to allow the native code to cache some
- * field offsets.
- */
- native private static void nativeClassInit();
-
- private native void init(int display);
-
- private final CompatibilityInfoHolder mCompatibilityInfo;
- private final int mDisplay;
- // Following fields are initialized from native code
- private int mPixelFormat;
- private float mRefreshRate;
- /*package*/ float mDensity;
- /*package*/ float mDpiX;
- /*package*/ float mDpiY;
-
- private final Point mTmpPoint = new Point();
- private final DisplayMetrics mTmpMetrics = new DisplayMetrics();
- private float mLastGetTime;
-
- private static final Object sStaticInit = new Object();
- private static boolean sInitialized = false;
- private static IWindowManager sWindowManager;
+ private void updateCachedAppSizeIfNeededLocked() {
+ long now = SystemClock.uptimeMillis();
+ if (now > mLastCachedAppSizeUpdate + CACHED_APP_SIZE_DURATION_MILLIS) {
+ updateDisplayInfoLocked();
+ mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
+ mCachedAppWidthCompat = mTempMetrics.widthPixels;
+ mCachedAppHeightCompat = mTempMetrics.heightPixels;
+ mLastCachedAppSizeUpdate = now;
+ }
+ }
- /**
- * Returns a display object which uses the metric's width/height instead.
- * @hide
- */
- public static Display createCompatibleDisplay(int displayId, CompatibilityInfoHolder compat) {
- return new Display(displayId, compat);
+ // For debugging purposes
+ @Override
+ public String toString() {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ mDisplayInfo.getAppMetrics(mTempMetrics, mCompatibilityInfo);
+ return "Display id " + mDisplayId + ": " + mDisplayInfo
+ + ", " + mTempMetrics + ", isValid=" + mIsValid;
+ }
}
}
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 6c2e540..a919ffc 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -66,7 +66,7 @@ public abstract class DisplayEventReceiver {
@Override
protected void finalize() throws Throwable {
try {
- dispose();
+ dispose(true);
} finally {
super.finalize();
}
@@ -76,9 +76,17 @@ public abstract class DisplayEventReceiver {
* Disposes the receiver.
*/
public void dispose() {
+ dispose(false);
+ }
+
+ private void dispose(boolean finalized) {
if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
+ }
mCloseGuard.close();
}
+
if (mReceiverPtr != 0) {
nativeDispose(mReceiverPtr);
mReceiverPtr = 0;
@@ -93,9 +101,23 @@ public abstract class DisplayEventReceiver {
*
* @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
* timebase.
+ * @param builtInDisplayId The surface flinger built-in display id such as
+ * {@link Surface#BUILT_IN_DISPLAY_ID_MAIN}.
* @param frame The frame number. Increases by one for each vertical sync interval.
*/
- public void onVsync(long timestampNanos, int frame) {
+ public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
+ }
+
+ /**
+ * Called when a display hotplug event is received.
+ *
+ * @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
+ * timebase.
+ * @param builtInDisplayId The surface flinger built-in display id such as
+ * {@link Surface#BUILT_IN_DISPLAY_ID_HDMI}.
+ * @param connected True if the display is connected, false if it disconnected.
+ */
+ public void onHotplug(long timestampNanos, int builtInDisplayId, boolean connected) {
}
/**
@@ -113,7 +135,13 @@ public abstract class DisplayEventReceiver {
// Called from native code.
@SuppressWarnings("unused")
- private void dispatchVsync(long timestampNanos, int frame) {
- onVsync(timestampNanos, frame);
+ private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
+ onVsync(timestampNanos, builtInDisplayId, frame);
+ }
+
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchHotplug(long timestampNanos, int builtInDisplayId, boolean connected) {
+ onHotplug(timestampNanos, builtInDisplayId, connected);
}
}
diff --git a/core/java/android/view/DisplayInfo.aidl b/core/java/android/view/DisplayInfo.aidl
new file mode 100644
index 0000000..e679208
--- /dev/null
+++ b/core/java/android/view/DisplayInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.view;
+
+parcelable DisplayInfo;
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
new file mode 100644
index 0000000..c968ec5
--- /dev/null
+++ b/core/java/android/view/DisplayInfo.java
@@ -0,0 +1,310 @@
+/*
+ * 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.view;
+
+import android.content.res.CompatibilityInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DisplayMetrics;
+
+import libcore.util.Objects;
+
+/**
+ * Describes the characteristics of a particular logical display.
+ * @hide
+ */
+public final class DisplayInfo implements Parcelable {
+ /**
+ * The surface flinger layer stack associated with this logical display.
+ */
+ public int layerStack;
+
+ /**
+ * Display flags.
+ */
+ public int flags;
+
+ /**
+ * The human-readable name of the display.
+ */
+ public String name;
+
+ /**
+ * The width of the portion of the display that is available to applications, in pixels.
+ * Represents the size of the display minus any system decorations.
+ */
+ public int appWidth;
+
+ /**
+ * The height of the portion of the display that is available to applications, in pixels.
+ * Represents the size of the display minus any system decorations.
+ */
+ public int appHeight;
+
+ /**
+ * The smallest value of {@link #appWidth} that an application is likely to encounter,
+ * in pixels, excepting cases where the width may be even smaller due to the presence
+ * of a soft keyboard, for example.
+ */
+ public int smallestNominalAppWidth;
+
+ /**
+ * The smallest value of {@link #appHeight} that an application is likely to encounter,
+ * in pixels, excepting cases where the height may be even smaller due to the presence
+ * of a soft keyboard, for example.
+ */
+ public int smallestNominalAppHeight;
+
+ /**
+ * The largest value of {@link #appWidth} that an application is likely to encounter,
+ * in pixels, excepting cases where the width may be even larger due to system decorations
+ * such as the status bar being hidden, for example.
+ */
+ public int largestNominalAppWidth;
+
+ /**
+ * The largest value of {@link #appHeight} that an application is likely to encounter,
+ * in pixels, excepting cases where the height may be even larger due to system decorations
+ * such as the status bar being hidden, for example.
+ */
+ public int largestNominalAppHeight;
+
+ /**
+ * The logical width of the display, in pixels.
+ * Represents the usable size of the display which may be smaller than the
+ * physical size when the system is emulating a smaller display.
+ */
+ public int logicalWidth;
+
+ /**
+ * The logical height of the display, in pixels.
+ * Represents the usable size of the display which may be smaller than the
+ * physical size when the system is emulating a smaller display.
+ */
+ public int logicalHeight;
+
+ /**
+ * The rotation of the display relative to its natural orientation.
+ * May be one of {@link android.view.Surface#ROTATION_0},
+ * {@link android.view.Surface#ROTATION_90}, {@link android.view.Surface#ROTATION_180},
+ * {@link android.view.Surface#ROTATION_270}.
+ * <p>
+ * The value of this field is indeterminate if the logical display is presented on
+ * more than one physical display.
+ * </p>
+ */
+ public int rotation;
+
+ /**
+ * The refresh rate of this display in frames per second.
+ * <p>
+ * The value of this field is indeterminate if the logical display is presented on
+ * more than one physical display.
+ * </p>
+ */
+ public float refreshRate;
+
+ /**
+ * The logical display density which is the basis for density-independent
+ * pixels.
+ */
+ public int logicalDensityDpi;
+
+ /**
+ * The exact physical pixels per inch of the screen in the X dimension.
+ * <p>
+ * The value of this field is indeterminate if the logical display is presented on
+ * more than one physical display.
+ * </p>
+ */
+ public float physicalXDpi;
+
+ /**
+ * The exact physical pixels per inch of the screen in the Y dimension.
+ * <p>
+ * The value of this field is indeterminate if the logical display is presented on
+ * more than one physical display.
+ * </p>
+ */
+ public float physicalYDpi;
+
+ public static final Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
+ public DisplayInfo createFromParcel(Parcel source) {
+ return new DisplayInfo(source);
+ }
+
+ public DisplayInfo[] newArray(int size) {
+ return new DisplayInfo[size];
+ }
+ };
+
+ public DisplayInfo() {
+ }
+
+ public DisplayInfo(DisplayInfo other) {
+ copyFrom(other);
+ }
+
+ private DisplayInfo(Parcel source) {
+ readFromParcel(source);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof DisplayInfo && equals((DisplayInfo)o);
+ }
+
+ public boolean equals(DisplayInfo other) {
+ return other != null
+ && layerStack == other.layerStack
+ && Objects.equal(name, other.name)
+ && appWidth == other.appWidth
+ && appHeight == other.appHeight
+ && smallestNominalAppWidth == other.smallestNominalAppWidth
+ && smallestNominalAppHeight == other.smallestNominalAppHeight
+ && largestNominalAppWidth == other.largestNominalAppWidth
+ && largestNominalAppHeight == other.largestNominalAppHeight
+ && logicalWidth == other.logicalWidth
+ && logicalHeight == other.logicalHeight
+ && rotation == other.rotation
+ && refreshRate == other.refreshRate
+ && logicalDensityDpi == other.logicalDensityDpi
+ && physicalXDpi == other.physicalXDpi
+ && physicalYDpi == other.physicalYDpi;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0; // don't care
+ }
+
+ public void copyFrom(DisplayInfo other) {
+ layerStack = other.layerStack;
+ flags = other.flags;
+ name = other.name;
+ appWidth = other.appWidth;
+ appHeight = other.appHeight;
+ smallestNominalAppWidth = other.smallestNominalAppWidth;
+ smallestNominalAppHeight = other.smallestNominalAppHeight;
+ largestNominalAppWidth = other.largestNominalAppWidth;
+ largestNominalAppHeight = other.largestNominalAppHeight;
+ logicalWidth = other.logicalWidth;
+ logicalHeight = other.logicalHeight;
+ rotation = other.rotation;
+ refreshRate = other.refreshRate;
+ logicalDensityDpi = other.logicalDensityDpi;
+ physicalXDpi = other.physicalXDpi;
+ physicalYDpi = other.physicalYDpi;
+ }
+
+ public void readFromParcel(Parcel source) {
+ layerStack = source.readInt();
+ flags = source.readInt();
+ name = source.readString();
+ appWidth = source.readInt();
+ appHeight = source.readInt();
+ smallestNominalAppWidth = source.readInt();
+ smallestNominalAppHeight = source.readInt();
+ largestNominalAppWidth = source.readInt();
+ largestNominalAppHeight = source.readInt();
+ logicalWidth = source.readInt();
+ logicalHeight = source.readInt();
+ rotation = source.readInt();
+ refreshRate = source.readFloat();
+ logicalDensityDpi = source.readInt();
+ physicalXDpi = source.readFloat();
+ physicalYDpi = source.readFloat();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(layerStack);
+ dest.writeInt(flags);
+ dest.writeString(name);
+ dest.writeInt(appWidth);
+ dest.writeInt(appHeight);
+ dest.writeInt(smallestNominalAppWidth);
+ dest.writeInt(smallestNominalAppHeight);
+ dest.writeInt(largestNominalAppWidth);
+ dest.writeInt(largestNominalAppHeight);
+ dest.writeInt(logicalWidth);
+ dest.writeInt(logicalHeight);
+ dest.writeInt(rotation);
+ dest.writeFloat(refreshRate);
+ dest.writeInt(logicalDensityDpi);
+ dest.writeFloat(physicalXDpi);
+ dest.writeFloat(physicalYDpi);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public void getAppMetrics(DisplayMetrics outMetrics, CompatibilityInfoHolder cih) {
+ getMetricsWithSize(outMetrics, cih, appWidth, appHeight);
+ }
+
+ public void getLogicalMetrics(DisplayMetrics outMetrics, CompatibilityInfoHolder cih) {
+ getMetricsWithSize(outMetrics, cih, logicalWidth, logicalHeight);
+ }
+
+ private void getMetricsWithSize(DisplayMetrics outMetrics, CompatibilityInfoHolder cih,
+ int width, int height) {
+ outMetrics.densityDpi = outMetrics.noncompatDensityDpi = logicalDensityDpi;
+ outMetrics.noncompatWidthPixels = outMetrics.widthPixels = width;
+ outMetrics.noncompatHeightPixels = outMetrics.heightPixels = height;
+
+ outMetrics.density = outMetrics.noncompatDensity =
+ logicalDensityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ outMetrics.scaledDensity = outMetrics.noncompatScaledDensity = outMetrics.density;
+ outMetrics.xdpi = outMetrics.noncompatXdpi = physicalXDpi;
+ outMetrics.ydpi = outMetrics.noncompatYdpi = physicalYDpi;
+
+ if (cih != null) {
+ CompatibilityInfo ci = cih.getIfNeeded();
+ if (ci != null) {
+ ci.applyToDisplayMetrics(outMetrics);
+ }
+ }
+ }
+
+ // For debugging purposes
+ @Override
+ public String toString() {
+ return "DisplayInfo{\"" + name + "\", app " + appWidth + " x " + appHeight
+ + ", real " + logicalWidth + " x " + logicalHeight
+ + ", largest app " + largestNominalAppWidth + " x " + largestNominalAppHeight
+ + ", smallest app " + smallestNominalAppWidth + " x " + smallestNominalAppHeight
+ + ", " + refreshRate + " fps"
+ + ", rotation " + rotation
+ + ", density " + logicalDensityDpi
+ + ", " + physicalXDpi + " x " + physicalYDpi + " dpi"
+ + ", layerStack " + layerStack + flagsToString(flags) + "}";
+ }
+
+ private static String flagsToString(int flags) {
+ StringBuilder result = new StringBuilder();
+ if ((flags & Display.FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT) != 0) {
+ result.append(", FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT");
+ }
+ if ((flags & Display.FLAG_SUPPORTS_SECURE_VIDEO_BUFFERS) != 0) {
+ result.append(", FLAG_SUPPORTS_SECURE_VIDEO_BUFFERS");
+ }
+ return result.toString();
+ }
+}
diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java
index a42e156..5e34a36 100644
--- a/core/java/android/view/DisplayList.java
+++ b/core/java/android/view/DisplayList.java
@@ -332,4 +332,11 @@ public abstract class DisplayList {
* @see View#offsetTopAndBottom(int)
*/
public abstract void offsetTopBottom(int offset);
+
+ /**
+ * Reset native resources. This is called when cleaning up the state of DisplayLists
+ * during destruction of hardware resources, to ensure that we do not hold onto
+ * obsolete resources after related resources are gone.
+ */
+ public abstract void reset();
}
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 31a9f05..b2988ed 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -79,17 +79,9 @@ public class FocusFinder {
}
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
- if ((direction & View.FOCUS_ACCESSIBILITY) != View.FOCUS_ACCESSIBILITY) {
- return findNextInputFocus(root, focused, focusedRect, direction);
- } else {
- return findNextAccessibilityFocus(root, focused, focusedRect, direction);
- }
- }
-
- private View findNextInputFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
View next = null;
if (focused != null) {
- next = findNextUserSpecifiedInputFocus(root, focused, direction);
+ next = findNextUserSpecifiedFocus(root, focused, direction);
}
if (next != null) {
return next;
@@ -107,7 +99,7 @@ public class FocusFinder {
return next;
}
- private View findNextUserSpecifiedInputFocus(ViewGroup root, View focused, int direction) {
+ private View findNextUserSpecifiedFocus(ViewGroup root, View focused, int direction) {
// check for user specified next focus
View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
if (userSetNextFocus != null && userSetNextFocus.isFocusable()
@@ -120,7 +112,6 @@ public class FocusFinder {
private View findNextFocus(ViewGroup root, View focused, Rect focusedRect,
int direction, ArrayList<View> focusables) {
- final int directionMasked = (direction & ~View.FOCUS_ACCESSIBILITY);
if (focused != null) {
if (focusedRect == null) {
focusedRect = mFocusedRect;
@@ -132,7 +123,7 @@ public class FocusFinder {
if (focusedRect == null) {
focusedRect = mFocusedRect;
// make up a rect at top left or bottom right of root
- switch (directionMasked) {
+ switch (direction) {
case View.FOCUS_RIGHT:
case View.FOCUS_DOWN:
setFocusTopLeft(root, focusedRect);
@@ -160,37 +151,23 @@ public class FocusFinder {
}
}
- switch (directionMasked) {
+ switch (direction) {
case View.FOCUS_FORWARD:
case View.FOCUS_BACKWARD:
- return findNextInputFocusInRelativeDirection(focusables, root, focused, focusedRect,
- directionMasked);
+ return findNextFocusInRelativeDirection(focusables, root, focused, focusedRect,
+ direction);
case View.FOCUS_UP:
case View.FOCUS_DOWN:
case View.FOCUS_LEFT:
case View.FOCUS_RIGHT:
- return findNextInputFocusInAbsoluteDirection(focusables, root, focused,
- focusedRect, directionMasked);
+ return findNextFocusInAbsoluteDirection(focusables, root, focused,
+ focusedRect, direction);
default:
- throw new IllegalArgumentException("Unknown direction: " + directionMasked);
- }
- }
-
- private View findNextAccessibilityFocus(ViewGroup root, View focused,
- Rect focusedRect, int direction) {
- ArrayList<View> focusables = mTempList;
- try {
- focusables.clear();
- root.addFocusables(focusables, direction, View.FOCUSABLES_ACCESSIBILITY);
- View next = findNextFocus(root, focused, focusedRect, direction,
- focusables);
- return next;
- } finally {
- focusables.clear();
+ throw new IllegalArgumentException("Unknown direction: " + direction);
}
}
- private View findNextInputFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root,
+ private View findNextFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root,
View focused, Rect focusedRect, int direction) {
try {
// Note: This sort is stable.
@@ -222,7 +199,7 @@ public class FocusFinder {
focusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
}
- View findNextInputFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
+ View findNextFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
Rect focusedRect, int direction) {
// initialize the best candidate to something impossible
// (so the first plausible view will become the best choice)
@@ -251,7 +228,7 @@ public class FocusFinder {
if (focusable == focused || focusable == root) continue;
// get focus bounds of other view in same coordinate system
- focusable.getFocusRect(mOtherRect);
+ focusable.getFocusedRect(mOtherRect);
root.offsetDescendantRectToMyCoords(focusable, mOtherRect);
if (isBetterCandidate(direction, focusedRect, mOtherRect, mBestCandidateRect)) {
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 055aee3..b64a06e 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -147,20 +147,36 @@ class GLES20Canvas extends HardwareCanvas {
///////////////////////////////////////////////////////////////////////////
// Hardware layers
///////////////////////////////////////////////////////////////////////////
-
+
+ @Override
+ void pushLayerUpdate(HardwareLayer layer) {
+ nPushLayerUpdate(mRenderer, ((GLES20RenderLayer) layer).mLayer);
+ }
+
+ @Override
+ void clearLayerUpdates() {
+ nClearLayerUpdates(mRenderer);
+ }
+
static native int nCreateTextureLayer(boolean opaque, int[] layerInfo);
static native int nCreateLayer(int width, int height, boolean isOpaque, int[] layerInfo);
- static native void nResizeLayer(int layerId, int width, int height, int[] layerInfo);
+ static native boolean nResizeLayer(int layerId, int width, int height, int[] layerInfo);
+ static native void nSetOpaqueLayer(int layerId, boolean isOpaque);
+ static native void nSetLayerPaint(int layerId, int nativePaint);
+ static native void nSetLayerColorFilter(int layerId, int nativeColorFilter);
static native void nUpdateTextureLayer(int layerId, int width, int height, boolean opaque,
SurfaceTexture surface);
+ static native void nClearLayerTexture(int layerId);
static native void nSetTextureLayerTransform(int layerId, int matrix);
static native void nDestroyLayer(int layerId);
static native void nDestroyLayerDeferred(int layerId);
- static native void nFlushLayer(int layerId);
static native void nUpdateRenderLayer(int layerId, int renderer, int displayList,
int left, int top, int right, int bottom);
static native boolean nCopyLayer(int layerId, int bitmap);
+ private static native void nClearLayerUpdates(int renderer);
+ private static native void nPushLayerUpdate(int renderer, int layer);
+
///////////////////////////////////////////////////////////////////////////
// Canvas management
///////////////////////////////////////////////////////////////////////////
@@ -394,13 +410,8 @@ class GLES20Canvas extends HardwareCanvas {
void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) {
final GLES20Layer glLayer = (GLES20Layer) layer;
- int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
- try {
- final int nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawLayer(mRenderer, glLayer.getLayer(), x, y, nativePaint);
- } finally {
- if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
- }
+ final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+ nDrawLayer(mRenderer, glLayer.getLayer(), x, y, nativePaint);
}
private static native void nDrawLayer(int renderer, int layer, float x, float y, int paint);
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
index 2d2e8e4..e9bd0c4 100644
--- a/core/java/android/view/GLES20DisplayList.java
+++ b/core/java/android/view/GLES20DisplayList.java
@@ -25,9 +25,12 @@ import java.util.ArrayList;
* An implementation of display list for OpenGL ES 2.0.
*/
class GLES20DisplayList extends DisplayList {
- // These lists ensure that any Bitmaps recorded by a DisplayList are kept alive as long
- // as the DisplayList is alive. The Bitmaps are populated by the GLES20RecordingCanvas.
+ // 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<Bitmap> mBitmaps = new ArrayList<Bitmap>(5);
+ final ArrayList<DisplayList> mChildDisplayLists = new ArrayList<DisplayList>();
private GLES20RecordingCanvas mCanvas;
private boolean mValid;
@@ -79,6 +82,14 @@ class GLES20DisplayList extends DisplayList {
public void clear() {
if (!mValid) {
mBitmaps.clear();
+ mChildDisplayLists.clear();
+ }
+ }
+
+ @Override
+ public void reset() {
+ if (hasNativeDisplayList()) {
+ nReset(mFinalizer.mNativeDisplayList);
}
}
@@ -290,6 +301,7 @@ class GLES20DisplayList extends DisplayList {
}
}
+ private static native void nReset(int displayList);
private static native void nOffsetTopBottom(int displayList, int offset);
private static native void nOffsetLeftRight(int displayList, int offset);
private static native void nSetLeftTopRightBottom(int displayList, int left, int top,
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
index 4f25792..812fb97 100644
--- a/core/java/android/view/GLES20Layer.java
+++ b/core/java/android/view/GLES20Layer.java
@@ -18,6 +18,7 @@
package android.view;
import android.graphics.Bitmap;
+import android.graphics.Paint;
/**
* An OpenGL ES 2.0 implementation of {@link HardwareLayer}.
@@ -43,13 +44,17 @@ abstract class GLES20Layer extends HardwareLayer {
}
@Override
- boolean copyInto(Bitmap bitmap) {
- return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap);
+ void setLayerPaint(Paint paint) {
+ if (paint != null) {
+ GLES20Canvas.nSetLayerPaint(mLayer, paint.mNativePaint);
+ GLES20Canvas.nSetLayerColorFilter(mLayer, paint.getColorFilter() != null ?
+ paint.getColorFilter().nativeColorFilter : 0);
+ }
}
-
+
@Override
- void update(int width, int height, boolean isOpaque) {
- super.update(width, height, isOpaque);
+ boolean copyInto(Bitmap bitmap) {
+ return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap);
}
@Override
@@ -60,12 +65,10 @@ abstract class GLES20Layer extends HardwareLayer {
}
mLayer = 0;
}
-
+
@Override
- void flush() {
- if (mLayer != 0) {
- GLES20Canvas.nFlushLayer(mLayer);
- }
+ void clearStorage() {
+ if (mLayer != 0) GLES20Canvas.nClearLayerTexture(mLayer);
}
static class Finalizer {
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index c9ba65f..ba8be6c 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -76,6 +76,7 @@ class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20Recor
void start() {
mDisplayList.mBitmaps.clear();
+ mDisplayList.mChildDisplayLists.clear();
}
int end(int nativeDisplayList) {
@@ -156,6 +157,13 @@ class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20Recor
}
@Override
+ public int drawDisplayList(DisplayList displayList, Rect dirty, int flags) {
+ int status = super.drawDisplayList(displayList, dirty, flags);
+ mDisplayList.mChildDisplayLists.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);
diff --git a/core/java/android/view/GLES20RenderLayer.java b/core/java/android/view/GLES20RenderLayer.java
index c727a36..44d4719 100644
--- a/core/java/android/view/GLES20RenderLayer.java
+++ b/core/java/android/view/GLES20RenderLayer.java
@@ -54,8 +54,8 @@ class GLES20RenderLayer extends GLES20Layer {
}
@Override
- void resize(int width, int height) {
- if (!isValid() || width <= 0 || height <= 0) return;
+ boolean resize(int width, int height) {
+ if (!isValid() || width <= 0 || height <= 0) return false;
mWidth = width;
mHeight = height;
@@ -63,11 +63,23 @@ class GLES20RenderLayer extends GLES20Layer {
if (width != mLayerWidth || height != mLayerHeight) {
int[] layerInfo = new int[2];
- GLES20Canvas.nResizeLayer(mLayer, width, height, layerInfo);
-
- mLayerWidth = layerInfo[0];
- mLayerHeight = layerInfo[1];
+ if (GLES20Canvas.nResizeLayer(mLayer, width, height, layerInfo)) {
+ mLayerWidth = layerInfo[0];
+ mLayerHeight = layerInfo[1];
+ } else {
+ // Failure: not enough GPU resources for requested size
+ mLayer = 0;
+ mLayerWidth = 0;
+ mLayerHeight = 0;
+ }
}
+ return isValid();
+ }
+
+ @Override
+ void setOpaque(boolean isOpaque) {
+ mOpaque = isOpaque;
+ GLES20Canvas.nSetOpaqueLayer(mLayer, isOpaque);
}
@Override
@@ -98,7 +110,7 @@ class GLES20RenderLayer extends GLES20Layer {
}
@Override
- void redraw(DisplayList displayList, Rect dirtyRect) {
+ void redrawLater(DisplayList displayList, Rect dirtyRect) {
GLES20Canvas.nUpdateRenderLayer(mLayer, mCanvas.getRenderer(),
((GLES20DisplayList) displayList).getNativeDisplayList(),
dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom);
diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java
index 16a13cf..e863e49 100644
--- a/core/java/android/view/GLES20TextureLayer.java
+++ b/core/java/android/view/GLES20TextureLayer.java
@@ -39,13 +39,7 @@ class GLES20TextureLayer extends GLES20Layer {
mFinalizer = new Finalizer(mLayer);
} else {
mFinalizer = null;
- }
- }
-
- GLES20TextureLayer(SurfaceTexture surface, boolean isOpaque) {
- this(isOpaque);
- mSurface = surface;
- mSurface.attachToGLContext(mTexture);
+ }
}
@Override
@@ -54,7 +48,8 @@ class GLES20TextureLayer extends GLES20Layer {
}
@Override
- void resize(int width, int height) {
+ boolean resize(int width, int height) {
+ return isValid();
}
@Override
@@ -93,11 +88,16 @@ class GLES20TextureLayer extends GLES20Layer {
}
@Override
+ void setOpaque(boolean isOpaque) {
+ throw new UnsupportedOperationException("Use update(int, int, boolean) instead");
+ }
+
+ @Override
void setTransform(Matrix matrix) {
GLES20Canvas.nSetTextureLayerTransform(mLayer, matrix.native_instance);
}
@Override
- void redraw(DisplayList displayList, Rect dirtyRect) {
+ void redrawLater(DisplayList displayList, Rect dirtyRect) {
}
}
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 4bbdd4e..9ddb32e 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -482,6 +482,27 @@ public class GestureDetector {
case MotionEvent.ACTION_POINTER_UP:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
+
+ // Check the dot product of current velocities.
+ // If the pointer that left was opposing another velocity vector, clear.
+ mVelocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
+ final int upIndex = ev.getActionIndex();
+ final int id1 = ev.getPointerId(upIndex);
+ final float x1 = mVelocityTracker.getXVelocity(id1);
+ final float y1 = mVelocityTracker.getYVelocity(id1);
+ for (int i = 0; i < count; i++) {
+ if (i == upIndex) continue;
+
+ final int id2 = ev.getPointerId(i);
+ final float x = x1 * mVelocityTracker.getXVelocity(id2);
+ final float y = y1 * mVelocityTracker.getYVelocity(id2);
+
+ final float dot = x + y;
+ if (dot < 0) {
+ mVelocityTracker.clear();
+ break;
+ }
+ }
break;
case MotionEvent.ACTION_DOWN:
diff --git a/core/java/android/view/Gravity.java b/core/java/android/view/Gravity.java
index 4547aa6..9a89fa5 100644
--- a/core/java/android/view/Gravity.java
+++ b/core/java/android/view/Gravity.java
@@ -153,9 +153,8 @@ public class Gravity
* container.
* @param layoutDirection The layout direction.
*
- * @see {@link View#LAYOUT_DIRECTION_LTR}
- * @see {@link View#LAYOUT_DIRECTION_RTL}
- * @hide
+ * @see View#LAYOUT_DIRECTION_LTR
+ * @see View#LAYOUT_DIRECTION_RTL
*/
public static void apply(int gravity, int w, int h, Rect container,
Rect outRect, int layoutDirection) {
@@ -291,9 +290,8 @@ public class Gravity
* container.
* @param layoutDirection The layout direction.
*
- * @see {@link View#LAYOUT_DIRECTION_LTR}
- * @see {@link View#LAYOUT_DIRECTION_RTL}
- * @hide
+ * @see View#LAYOUT_DIRECTION_LTR
+ * @see View#LAYOUT_DIRECTION_RTL
*/
public static void apply(int gravity, int w, int h, Rect container,
int xAdj, int yAdj, Rect outRect, int layoutDirection) {
@@ -372,9 +370,8 @@ public class Gravity
* modified if needed to fit in the display.
* @param layoutDirection The layout direction.
*
- * @see {@link View#LAYOUT_DIRECTION_LTR}
- * @see {@link View#LAYOUT_DIRECTION_RTL}
- * @hide
+ * @see View#LAYOUT_DIRECTION_LTR
+ * @see View#LAYOUT_DIRECTION_RTL
*/
public static void applyDisplay(int gravity, Rect display, Rect inoutObj, int layoutDirection) {
int absGravity = getAbsoluteGravity(gravity, layoutDirection);
@@ -411,7 +408,6 @@ public class Gravity
* @param gravity The gravity to convert to absolute (horizontal) values.
* @param layoutDirection The layout direction.
* @return gravity converted to absolute (horizontal) values.
- * @hide
*/
public static int getAbsoluteGravity(int gravity, int layoutDirection) {
int result = gravity;
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 777552a..eeae3ed 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -132,4 +132,20 @@ public abstract class HardwareCanvas extends Canvas {
* @see #detachFunctor(int)
*/
abstract void attachFunctor(int functor);
+
+ /**
+ * Indicates that the specified layer must be updated as soon as possible.
+ *
+ * @param layer The layer to update
+ *
+ * @see #clearLayerUpdates()
+ */
+ abstract void pushLayerUpdate(HardwareLayer layer);
+
+ /**
+ * Removes all enqueued layer updates.
+ *
+ * @see #pushLayerUpdate(HardwareLayer)
+ */
+ abstract void clearLayerUpdates();
}
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index e73f7bf..d3bc35a 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -19,6 +19,7 @@ package android.view;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
+import android.graphics.Paint;
import android.graphics.Rect;
/**
@@ -62,6 +63,14 @@ abstract class HardwareLayer {
}
/**
+ * Update the paint used when drawing this layer.
+ *
+ * @param paint The paint used when the layer is drawn into the destination canvas.
+ * @see View#setLayerPaint(android.graphics.Paint)
+ */
+ void setLayerPaint(Paint paint) {}
+
+ /**
* Returns the minimum width of the layer.
*
* @return The minimum desired width of the hardware layer
@@ -107,6 +116,13 @@ abstract class HardwareLayer {
}
/**
+ * Sets whether or not this layer should be considered opaque.
+ *
+ * @param isOpaque True if the layer is opaque, false otherwise
+ */
+ abstract void setOpaque(boolean isOpaque);
+
+ /**
* Indicates whether this layer can be rendered.
*
* @return True if the layer can be rendered into, false otherwise
@@ -119,8 +135,9 @@ abstract class HardwareLayer {
*
* @param width The new desired minimum width for this layer
* @param height The new desired minimum height for this layer
+ * @return True if the resulting layer is valid, false otherwise
*/
- abstract void resize(int width, int height);
+ abstract boolean resize(int width, int height);
/**
* Returns a hardware canvas that can be used to render onto
@@ -136,11 +153,6 @@ abstract class HardwareLayer {
abstract void destroy();
/**
- * Flush the render queue associated with this layer.
- */
- abstract void flush();
-
- /**
* This must be invoked before drawing onto this layer.
* @param currentCanvas
*/
@@ -191,5 +203,10 @@ abstract class HardwareLayer {
* execute in this layer
* @param dirtyRect The dirty region of the layer that needs to be redrawn
*/
- abstract void redraw(DisplayList displayList, Rect dirtyRect);
+ abstract void redrawLater(DisplayList displayList, Rect dirtyRect);
+
+ /**
+ * Indicates that this layer has lost its underlying storage.
+ */
+ abstract void clearStorage();
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index dab48b1..99987bf 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -21,6 +21,7 @@ import android.content.ComponentCallbacks2;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
import android.opengl.GLUtils;
import android.opengl.ManagedEGLContext;
import android.os.Handler;
@@ -135,7 +136,30 @@ public abstract class HardwareRenderer {
* @hide
*/
public static final String DEBUG_DIRTY_REGIONS_PROPERTY = "debug.hwui.show_dirty_regions";
-
+
+ /**
+ * Turn on to flash hardware layers when they update.
+ *
+ * Possible values:
+ * "true", to enable hardware layers updates debugging
+ * "false", to disable hardware layers updates debugging
+ *
+ * @hide
+ */
+ public static final String DEBUG_SHOW_LAYERS_UPDATES_PROPERTY =
+ "debug.hwui.show_layers_updates";
+
+ /**
+ * Turn on to show overdraw level.
+ *
+ * Possible values:
+ * "true", to enable overdraw debugging
+ * "false", to disable overdraw debugging
+ *
+ * @hide
+ */
+ public static final String DEBUG_SHOW_OVERDRAW_PROPERTY = "debug.hwui.show_overdraw";
+
/**
* A process can set this flag to false to prevent the use of hardware
* rendering.
@@ -197,18 +221,18 @@ public abstract class HardwareRenderer {
/**
* Initializes the hardware renderer for the specified surface.
*
- * @param holder The holder for the surface to hardware accelerate.
+ * @param surface The surface to hardware accelerate
*
* @return True if the initialization was successful, false otherwise.
*/
- abstract boolean initialize(SurfaceHolder holder) throws Surface.OutOfResourcesException;
+ abstract boolean initialize(Surface surface) throws Surface.OutOfResourcesException;
/**
* Updates the hardware renderer for the specified surface.
- *
- * @param holder The holder for the surface to hardware accelerate
+ *
+ * @param surface The surface to hardware accelerate
*/
- abstract void updateSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException;
+ abstract void updateSurface(Surface surface) throws Surface.OutOfResourcesException;
/**
* Destroys the layers used by the specified view hierarchy.
@@ -228,10 +252,10 @@ public abstract class HardwareRenderer {
/**
* This method should be invoked whenever the current hardware renderer
* context should be reset.
- *
- * @param holder The holder for the surface to hardware accelerate
+ *
+ * @param surface The surface to hardware accelerate
*/
- abstract void invalidate(SurfaceHolder holder);
+ abstract void invalidate(Surface surface);
/**
* This method should be invoked to ensure the hardware renderer is in
@@ -358,6 +382,14 @@ public abstract class HardwareRenderer {
private static native void nDisableVsync();
/**
+ * Indicates that the specified hardware layer needs to be updated
+ * as soon as possible.
+ *
+ * @param layer The hardware layer that needs an update
+ */
+ abstract void pushLayerUpdate(HardwareLayer layer);
+
+ /**
* Interface used to receive callbacks whenever a view is drawn by
* a hardware renderer instance.
*/
@@ -474,14 +506,14 @@ public abstract class HardwareRenderer {
*
* @param width The width of the drawing surface.
* @param height The height of the drawing surface.
- * @param holder The target surface
+ * @param surface The surface to hardware accelerate
*/
- void initializeIfNeeded(int width, int height, SurfaceHolder holder)
+ void initializeIfNeeded(int width, int height, Surface surface)
throws Surface.OutOfResourcesException {
if (isRequested()) {
// We lost the gl context, so recreate it.
if (!isEnabled()) {
- if (initialize(holder)) {
+ if (initialize(surface)) {
setup(width, height);
}
}
@@ -577,12 +609,6 @@ public abstract class HardwareRenderer {
@SuppressWarnings({"deprecation"})
static abstract class GlRenderer extends HardwareRenderer {
- // These values are not exposed in our EGL APIs
- static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
- static final int EGL_OPENGL_ES2_BIT = 4;
- static final int EGL_SURFACE_TYPE = 0x3033;
- static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;
-
static final int SURFACE_STATE_ERROR = 0;
static final int SURFACE_STATE_SUCCESS = 1;
static final int SURFACE_STATE_UPDATED = 2;
@@ -629,6 +655,7 @@ public abstract class HardwareRenderer {
int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
final boolean mDebugDirtyRegions;
+ final boolean mShowOverdraw;
final int mGlVersion;
final boolean mTranslucent;
@@ -678,6 +705,9 @@ public abstract class HardwareRenderer {
if (mDebugDirtyRegions) {
Log.d(LOG_TAG, "Debugging dirty regions");
}
+
+ mShowOverdraw = SystemProperties.getBoolean(
+ HardwareRenderer.DEBUG_SHOW_OVERDRAW_PROPERTY, false);
}
@Override
@@ -721,13 +751,17 @@ public abstract class HardwareRenderer {
*/
void checkEglErrors() {
if (isEnabled()) {
- int error = sEgl.eglGetError();
- if (error != EGL_SUCCESS) {
- // something bad has happened revert to
- // normal rendering.
- Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error));
- fallback(error != EGL11.EGL_CONTEXT_LOST);
- }
+ checkEglErrorsForced();
+ }
+ }
+
+ private void checkEglErrorsForced() {
+ int error = sEgl.eglGetError();
+ if (error != EGL_SUCCESS) {
+ // something bad has happened revert to
+ // normal rendering.
+ Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error));
+ fallback(error != EGL11.EGL_CONTEXT_LOST);
}
}
@@ -742,10 +776,10 @@ public abstract class HardwareRenderer {
}
@Override
- boolean initialize(SurfaceHolder holder) throws Surface.OutOfResourcesException {
+ boolean initialize(Surface surface) throws Surface.OutOfResourcesException {
if (isRequested() && !isEnabled()) {
initializeEgl();
- mGl = createEglSurface(holder);
+ mGl = createEglSurface(surface);
mDestroyed = false;
if (mGl != null) {
@@ -771,9 +805,9 @@ public abstract class HardwareRenderer {
}
@Override
- void updateSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException {
+ void updateSurface(Surface surface) throws Surface.OutOfResourcesException {
if (isRequested() && isEnabled()) {
- createEglSurface(holder);
+ createEglSurface(surface);
}
}
@@ -800,7 +834,9 @@ public abstract class HardwareRenderer {
throw new RuntimeException("eglInitialize failed " +
GLUtils.getEGLErrorString(sEgl.eglGetError()));
}
-
+
+ checkEglErrorsForced();
+
sEglConfig = chooseEglConfig();
if (sEglConfig == null) {
// We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without
@@ -888,7 +924,7 @@ public abstract class HardwareRenderer {
Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0]));
}
- GL createEglSurface(SurfaceHolder holder) throws Surface.OutOfResourcesException {
+ GL createEglSurface(Surface surface) throws Surface.OutOfResourcesException {
// Check preconditions.
if (sEgl == null) {
throw new RuntimeException("egl not initialized");
@@ -908,23 +944,12 @@ public abstract class HardwareRenderer {
destroySurface();
// Create an EGL surface we can render into.
- if (!createSurface(holder)) {
+ if (!createSurface(surface)) {
return null;
}
- /*
- * Before we can issue GL commands, we need to make sure
- * the context is current and bound to a surface.
- */
- if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
- throw new Surface.OutOfResourcesException("eglMakeCurrent failed "
- + GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
initCaches();
- enableDirtyRegions();
-
return mEglContext.getGL();
}
@@ -949,10 +974,17 @@ public abstract class HardwareRenderer {
abstract void initCaches();
EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
- int[] attribs = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE };
-
- return egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT,
- mGlVersion != 0 ? attribs : null);
+ int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE };
+
+ EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT,
+ mGlVersion != 0 ? attribs : null);
+ if (context == null || context == EGL_NO_CONTEXT) {
+ //noinspection ConstantConditions
+ throw new IllegalStateException(
+ "Could not create an EGL context. eglCreateContext failed with error: " +
+ GLUtils.getEGLErrorString(sEgl.eglGetError()));
+ }
+ return context;
}
@Override
@@ -982,7 +1014,7 @@ public abstract class HardwareRenderer {
}
@Override
- void invalidate(SurfaceHolder holder) {
+ void invalidate(Surface surface) {
// Cancels any existing buffer to ensure we'll get a buffer
// of the right size before we call eglSwapBuffers
sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
@@ -993,8 +1025,8 @@ public abstract class HardwareRenderer {
setEnabled(false);
}
- if (holder.getSurface().isValid()) {
- if (!createSurface(holder)) {
+ if (surface.isValid()) {
+ if (!createSurface(surface)) {
return;
}
@@ -1006,8 +1038,8 @@ public abstract class HardwareRenderer {
}
}
- private boolean createSurface(SurfaceHolder holder) {
- mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, holder, null);
+ private boolean createSurface(Surface surface) {
+ mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, surface, null);
if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
int error = sEgl.eglGetError();
@@ -1018,6 +1050,14 @@ public abstract class HardwareRenderer {
throw new RuntimeException("createWindowSurface failed "
+ GLUtils.getEGLErrorString(error));
}
+
+ if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+ throw new IllegalStateException("eglMakeCurrent failed " +
+ GLUtils.getEGLErrorString(sEgl.eglGetError()));
+ }
+
+ enableDirtyRegions();
+
return true;
}
@@ -1089,7 +1129,7 @@ public abstract class HardwareRenderer {
attachInfo.mIgnoreDirtyState = true;
attachInfo.mDrawingTime = SystemClock.uptimeMillis();
- view.mPrivateFlags |= View.DRAWN;
+ view.mPrivateFlags |= View.PFLAG_DRAWN;
final int surfaceState = checkCurrent();
if (surfaceState != SURFACE_STATE_ERROR) {
@@ -1118,14 +1158,13 @@ public abstract class HardwareRenderer {
}
}
- int status = onPreDraw(dirty);
- int saveCount = canvas.save();
- callbacks.onHardwarePreDraw(canvas);
+ int saveCount = 0;
+ int status = DisplayList.STATUS_DONE;
try {
- view.mRecreateDisplayList =
- (view.mPrivateFlags & View.INVALIDATED) == View.INVALIDATED;
- view.mPrivateFlags &= ~View.INVALIDATED;
+ view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
+ == View.PFLAG_INVALIDATED;
+ view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
long getDisplayListStartTime = 0;
if (mProfileEnabled) {
@@ -1137,8 +1176,9 @@ public abstract class HardwareRenderer {
getDisplayListStartTime = System.nanoTime();
}
- DisplayList displayList;
+ canvas.clearLayerUpdates();
+ DisplayList displayList;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
try {
displayList = view.getDisplayList();
@@ -1146,6 +1186,10 @@ public abstract class HardwareRenderer {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
+ status = onPreDraw(dirty);
+ saveCount = canvas.save();
+ callbacks.onHardwarePreDraw(canvas);
+
if (mProfileEnabled) {
long now = System.nanoTime();
float total = (now - getDisplayListStartTime) * 0.000001f;
@@ -1378,15 +1422,16 @@ public abstract class HardwareRenderer {
@Override
int[] getConfig(boolean dirtyRegions) {
return new int[] {
- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 0,
- EGL_STENCIL_SIZE, GLES20Canvas.getStencilSize(),
+ // TODO: Find a better way to choose the stencil size
+ EGL_STENCIL_SIZE, mShowOverdraw ? GLES20Canvas.getStencilSize() : 0,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT |
- (dirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
+ (dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
EGL_NONE
};
}
@@ -1431,6 +1476,11 @@ public abstract class HardwareRenderer {
}
@Override
+ void pushLayerUpdate(HardwareLayer layer) {
+ mGlCanvas.pushLayerUpdate(layer);
+ }
+
+ @Override
public DisplayList createDisplayList(String name) {
return new GLES20DisplayList(name);
}
@@ -1458,6 +1508,9 @@ public abstract class HardwareRenderer {
@Override
void destroyLayers(View view) {
if (view != null && isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) {
+ if (mCanvas != null) {
+ mCanvas.clearLayerUpdates();
+ }
destroyHardwareLayer(view);
GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
}
@@ -1506,6 +1559,9 @@ public abstract class HardwareRenderer {
safelyRun(new Runnable() {
@Override
public void run() {
+ if (mCanvas != null) {
+ mCanvas.clearLayerUpdates();
+ }
destroyResources(view);
GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
}
diff --git a/core/java/android/view/IDisplayContentChangeListener.aidl b/core/java/android/view/IDisplayContentChangeListener.aidl
new file mode 100644
index 0000000..8f23ff6
--- /dev/null
+++ b/core/java/android/view/IDisplayContentChangeListener.aidl
@@ -0,0 +1,32 @@
+/*
+** Copyright 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.view;
+
+import android.os.IBinder;
+import android.view.WindowInfo;
+import android.graphics.Rect;
+
+/**
+ * Interface for observing content changes on a display.
+ *
+ * {@hide}
+ */
+oneway interface IDisplayContentChangeListener {
+ void onWindowTransition(int displayId, int transition, in WindowInfo info);
+ void onRectangleOnScreenRequested(int displayId, in Rect rectangle, boolean immediate);
+ void onRotationChanged(int rotation);
+}
diff --git a/core/java/android/view/IInputFilter.aidl b/core/java/android/view/IInputFilter.aidl
new file mode 100644
index 0000000..fead5f6
--- /dev/null
+++ b/core/java/android/view/IInputFilter.aidl
@@ -0,0 +1,32 @@
+/**
+ * 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.view;
+
+import android.view.IInputFilterHost;
+import android.view.InputEvent;
+
+/**
+ * Interface for implementing an filter which observes and
+ * potentially transforms the input event stream in the system.
+ *
+ * @hide
+ */
+oneway interface IInputFilter {
+ void install(IInputFilterHost host);
+ void uninstall();
+ void filterInputEvent(in InputEvent event, int policyFlags);
+}
diff --git a/core/java/android/view/IInputFilterHost.aidl b/core/java/android/view/IInputFilterHost.aidl
new file mode 100644
index 0000000..93b8239
--- /dev/null
+++ b/core/java/android/view/IInputFilterHost.aidl
@@ -0,0 +1,28 @@
+/**
+ * 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.view;
+
+import android.view.InputEvent;
+
+/**
+ * Interface for calls from an input filter to its host.
+ *
+ * @hide
+ */
+oneway interface IInputFilterHost {
+ void sendInputEvent(in InputEvent event, int policyFlags);
+}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index b4caad3..15bd46c 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -45,8 +45,9 @@ oneway interface IWindow {
*/
void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor);
- void resized(int w, int h, in Rect contentInsets,
+ void resized(in Rect frame, in Rect contentInsets,
in Rect visibleInsets, boolean reportDraw, in Configuration newConfig);
+ void moved(int newX, int newY);
void dispatchAppVisibility(boolean visible);
void dispatchGetNewSurface();
void dispatchScreenState(boolean on);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 4d4eec7..a64cbf7 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -23,8 +23,10 @@ import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Point;
+import android.graphics.Rect;
import android.os.IRemoteCallback;
import android.view.IApplicationToken;
+import android.view.IDisplayContentChangeListener;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
import android.view.IWindowSession;
@@ -33,6 +35,8 @@ import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.InputChannel;
import android.view.InputDevice;
+import android.view.IInputFilter;
+import android.view.WindowInfo;
/**
* System private interface to the window manager.
@@ -54,14 +58,11 @@ interface IWindowManager
IWindowSession openSession(in IInputMethodClient client,
in IInputContext inputContext);
boolean inputMethodClientHasFocus(IInputMethodClient client);
-
- void getDisplaySize(out Point size);
- void getRealDisplaySize(out Point size);
- int getMaximumSizeDimension();
- void getCurrentSizeRange(out Point smallestSize, out Point largestSize);
- void setForcedDisplaySize(int longDimen, int shortDimen);
- void clearForcedDisplaySize();
+ void setForcedDisplaySize(int displayId, int width, int height);
+ void clearForcedDisplaySize(int displayId);
+ void setForcedDisplayDensity(int displayId, int density);
+ void clearForcedDisplayDensity(int displayId);
// Is the device configured to have a full system bar for larger screens?
boolean hasSystemNavBar();
@@ -85,7 +86,7 @@ interface IWindowManager
void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
int startHeight);
void overridePendingAppTransitionThumb(in Bitmap srcThumb, int startX, int startY,
- IRemoteCallback startedCallback, boolean delayed);
+ IRemoteCallback startedCallback, boolean scaleUp);
void executeAppTransition();
void setAppStartingWindow(IBinder token, String pkg, int theme,
in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
@@ -106,6 +107,9 @@ interface IWindowManager
IBinder freezeThisOneIfNeeded);
void setNewConfiguration(in Configuration config);
+ void startFreezingScreen(int exitAnim, int enterAnim);
+ void stopFreezingScreen();
+
// these require DISABLE_KEYGUARD permission
void disableKeyguard(IBinder token, String tag);
void reenableKeyguard(IBinder token);
@@ -187,7 +191,7 @@ interface IWindowManager
/**
* Create a screenshot of the applications currently displayed.
*/
- Bitmap screenshotApplications(IBinder appToken, int maxWidth, int maxHeight);
+ Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight);
/**
* Called by the status bar to notify Views of changes to System UI visiblity.
@@ -208,4 +212,44 @@ interface IWindowManager
* Lock the device immediately.
*/
void lockNow();
+
+ /**
+ * Gets the token for the focused window.
+ */
+ IBinder getFocusedWindowToken();
+
+ /**
+ * Gets the compatibility scale of e window given its token.
+ */
+ float getWindowCompatibilityScale(IBinder windowToken);
+
+ /**
+ * Sets an input filter for manipulating the input event stream.
+ */
+ void setInputFilter(in IInputFilter filter);
+
+ /**
+ * Sets the scale and offset for implementing accessibility magnification.
+ */
+ void magnifyDisplay(int dipslayId, float scale, float offsetX, float offsetY);
+
+ /**
+ * Adds a listener for display content changes.
+ */
+ void addDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener);
+
+ /**
+ * Removes a listener for display content changes.
+ */
+ void removeDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener);
+
+ /**
+ * Gets the info for a window given its token.
+ */
+ WindowInfo getWindowInfo(IBinder token);
+
+ /**
+ * Gets the infos for all visible windows.
+ */
+ void getVisibleWindowsForDisplay(int displayId, out List<WindowInfo> outInfos);
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index d4a03ce..ff9dcce 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -37,8 +37,13 @@ interface IWindowSession {
int add(IWindow window, int seq, in WindowManager.LayoutParams attrs,
in int viewVisibility, out Rect outContentInsets,
out InputChannel outInputChannel);
+ int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs,
+ in int viewVisibility, in int layerStackId, out Rect outContentInsets,
+ out InputChannel outInputChannel);
int addWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
in int viewVisibility, out Rect outContentInsets);
+ int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
+ in int viewVisibility, in int layerStackId, out Rect outContentInsets);
void remove(IWindow window);
/**
@@ -54,8 +59,8 @@ interface IWindowSession {
* @param requestedWidth The width the window wants to be.
* @param requestedHeight The height the window wants to be.
* @param viewVisibility Window root view's visibility.
- * @param flags Request flags: {@link WindowManagerImpl#RELAYOUT_INSETS_PENDING},
- * {@link WindowManagerImpl#RELAYOUT_DEFER_SURFACE_DESTROY}.
+ * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING},
+ * {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}.
* @param outFrame Rect in which is placed the new position/size on
* screen.
* @param outContentInsets Rect in which is placed the offsets from
@@ -74,8 +79,8 @@ interface IWindowSession {
* was last displayed.
* @param outSurface Object in which is placed the new display surface.
*
- * @return int Result flags: {@link WindowManagerImpl#RELAYOUT_SHOW_FOCUS},
- * {@link WindowManagerImpl#RELAYOUT_FIRST_TIME}.
+ * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS},
+ * {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
*/
int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
@@ -172,4 +177,12 @@ interface IWindowSession {
int z, in Bundle extras, boolean sync);
void wallpaperCommandComplete(IBinder window, in Bundle result);
+
+ void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
+ float dsdx, float dtdx, float dsdy, float dtdy);
+
+ /**
+ * Notifies that a rectangle on the screen has been requested.
+ */
+ void onRectangleOnScreenRequested(IBinder token, in Rect rectangle, boolean immediate);
}
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index fafe416..5dda934 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -322,7 +322,7 @@ public final class InputEventConsistencyVerifier {
final int action = event.getAction();
final boolean newStream = action == MotionEvent.ACTION_DOWN
- || action == MotionEvent.ACTION_CANCEL;
+ || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_OUTSIDE;
if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled)) {
mTouchEventStreamIsTainted = false;
mTouchEventStreamUnhandled = false;
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 9c56782..117c101 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -73,7 +73,7 @@ public abstract class InputEventReceiver {
@Override
protected void finalize() throws Throwable {
try {
- dispose();
+ dispose(true);
} finally {
super.finalize();
}
@@ -83,9 +83,17 @@ public abstract class InputEventReceiver {
* Disposes the receiver.
*/
public void dispose() {
+ dispose(false);
+ }
+
+ private void dispose(boolean finalized) {
if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
+ }
mCloseGuard.close();
}
+
if (mReceiverPtr != 0) {
nativeDispose(mReceiverPtr);
mReceiverPtr = 0;
diff --git a/core/java/android/view/InputFilter.java b/core/java/android/view/InputFilter.java
new file mode 100644
index 0000000..c25b87b
--- /dev/null
+++ b/core/java/android/view/InputFilter.java
@@ -0,0 +1,261 @@
+/*
+ * 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.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.view.IInputFilter;
+import android.view.InputEvent;
+import android.view.InputEventConsistencyVerifier;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.WindowManagerPolicy;
+
+/**
+ * Filters input events before they are dispatched to the system.
+ * <p>
+ * At most one input filter can be installed by calling
+ * {@link WindowManagerService#setInputFilter}. When an input filter is installed, the
+ * system's behavior changes as follows:
+ * <ul>
+ * <li>Input events are first delivered to the {@link WindowManagerPolicy}
+ * interception methods before queuing as usual. This critical step takes care of managing
+ * the power state of the device and handling wake keys.</li>
+ * <li>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
+ * injected into the system by other means, such as by instrumentation.</li>
+ * <li>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
+ * a different sequence of motion events, key presses or other system-level interactions.
+ * The input filter can send events to be dispatched by calling
+ * {@link #sendInputEvent(InputEvent)} and passing appropriate policy flags for the
+ * input event.</li>
+ * </ul>
+ * </p>
+ * <h3>The importance of input event consistency</h3>
+ * <p>
+ * The input filter mechanism is very low-level. At a minimum, it needs to ensure that it
+ * sends an internally consistent stream of input events to the dispatcher. There are
+ * very important invariants to be maintained.
+ * </p><p>
+ * For example, if a key down is sent, a corresponding key up should also be sent eventually.
+ * Likewise, for touch events, each pointer must individually go down with
+ * {@link MotionEvent#ACTION_DOWN} or {@link MotionEvent#ACTION_POINTER_DOWN} and then
+ * individually go up with {@link MotionEvent#ACTION_POINTER_UP} or {@link MotionEvent#ACTION_UP}
+ * and the sequence of pointer ids used must be consistent throughout the gesture.
+ * </p><p>
+ * Sometimes a filter may wish to cancel a previously dispatched key or motion. It should
+ * use {@link KeyEvent#FLAG_CANCELED} or {@link MotionEvent#ACTION_CANCEL} accordingly.
+ * </p><p>
+ * 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,
+ * touches and other input: they must be kept separate.
+ * </p>
+ * <h3>Policy flags</h3>
+ * <p>
+ * Input events received from the dispatcher and sent to the dispatcher have policy flags
+ * associated with them. Policy flags control some functions of the dispatcher.
+ * </p><p>
+ * The early policy interception decides whether an input event should be delivered
+ * to applications or dropped. The policy indicates its decision by setting the
+ * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} policy flag. The input filter may
+ * sometimes receive events that do not have this flag set. It should take note of
+ * the fact that the policy intends to drop the event, clean up its state, and
+ * then send appropriate cancellation events to the dispatcher if needed.
+ * </p><p>
+ * For example, suppose the input filter is processing a gesture and one of the touch events
+ * it receives does not have the {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag set.
+ * 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.
+ * </p><p>
+ * Corollary: Events that set sent to the dispatcher should usually include the
+ * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag. Otherwise, they will be dropped!
+ * </p><p>
+ * It may be prudent to disable automatic key repeating for synthetic key events
+ * by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag.
+ * </p>
+ *
+ * @hide
+ */
+public abstract class InputFilter extends IInputFilter.Stub {
+ private static final int MSG_INSTALL = 1;
+ private static final int MSG_UNINSTALL = 2;
+ private static final int MSG_INPUT_EVENT = 3;
+
+ // Consistency verifiers for debugging purposes.
+ private final InputEventConsistencyVerifier mInboundInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this,
+ InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT,
+ "InputFilter#InboundInputEventConsistencyVerifier") : null;
+ private final InputEventConsistencyVerifier mOutboundInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this,
+ InputEventConsistencyVerifier.FLAG_RAW_DEVICE_INPUT,
+ "InputFilter#OutboundInputEventConsistencyVerifier") : null;
+
+ private final H mH;
+
+ private IInputFilterHost mHost;
+
+ /**
+ * Creates the input filter.
+ *
+ * @param looper The looper to run callbacks on.
+ */
+ public InputFilter(Looper looper) {
+ mH = new H(looper);
+ }
+
+ /**
+ * Called when the input filter is installed.
+ * This method is guaranteed to be non-reentrant.
+ *
+ * @param host The input filter host environment.
+ */
+ public final void install(IInputFilterHost host) {
+ mH.obtainMessage(MSG_INSTALL, host).sendToTarget();
+ }
+
+ /**
+ * Called when the input filter is uninstalled.
+ * This method is guaranteed to be non-reentrant.
+ */
+ public final void uninstall() {
+ mH.obtainMessage(MSG_UNINSTALL).sendToTarget();
+ }
+
+ /**
+ * Called to enqueue the input event for filtering.
+ * The event will be recycled after the input filter processes it.
+ * This method is guaranteed to be non-reentrant.
+ *
+ * @param event The input event to enqueue.
+ */
+ final public void filterInputEvent(InputEvent event, int policyFlags) {
+ mH.obtainMessage(MSG_INPUT_EVENT, policyFlags, 0, event).sendToTarget();
+ }
+
+ /**
+ * Sends an input event to the dispatcher.
+ *
+ * @param event The input event to publish.
+ * @param policyFlags The input event policy flags.
+ */
+ public void sendInputEvent(InputEvent event, int policyFlags) {
+ if (event == null) {
+ throw new IllegalArgumentException("event must not be null");
+ }
+ if (mHost == null) {
+ throw new IllegalStateException("Cannot send input event because the input filter " +
+ "is not installed.");
+ }
+ if (mOutboundInputEventConsistencyVerifier != null) {
+ mOutboundInputEventConsistencyVerifier.onInputEvent(event, 0);
+ }
+ try {
+ mHost.sendInputEvent(event, policyFlags);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+
+ /**
+ * Called when an input event has been received from the dispatcher.
+ * <p>
+ * The default implementation sends the input event back to the dispatcher, unchanged.
+ * </p><p>
+ * The event will be recycled when this method returns. If you want to keep it around,
+ * make a copy!
+ * </p>
+ *
+ * @param event The input event that was received.
+ * @param policyFlags The input event policy flags.
+ */
+ public void onInputEvent(InputEvent event, int policyFlags) {
+ sendInputEvent(event, policyFlags);
+ }
+
+ /**
+ * Called when the filter is installed into the dispatch pipeline.
+ * <p>
+ * This method is called before the input filter receives any input events.
+ * The input filter should take this opportunity to prepare itself.
+ * </p>
+ */
+ public void onInstalled() {
+ }
+
+ /**
+ * Called when the filter is uninstalled from the dispatch pipeline.
+ * <p>
+ * This method is called after the input filter receives its last input event.
+ * The input filter should take this opportunity to clean up.
+ * </p>
+ */
+ public void onUninstalled() {
+ }
+
+ private final class H extends Handler {
+ public H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_INSTALL:
+ mHost = (IInputFilterHost) msg.obj;
+ if (mInboundInputEventConsistencyVerifier != null) {
+ mInboundInputEventConsistencyVerifier.reset();
+ }
+ if (mOutboundInputEventConsistencyVerifier != null) {
+ mOutboundInputEventConsistencyVerifier.reset();
+ }
+ onInstalled();
+ break;
+
+ case MSG_UNINSTALL:
+ try {
+ onUninstalled();
+ } finally {
+ mHost = null;
+ }
+ break;
+
+ case MSG_INPUT_EVENT: {
+ final InputEvent event = (InputEvent)msg.obj;
+ try {
+ if (mInboundInputEventConsistencyVerifier != null) {
+ mInboundInputEventConsistencyVerifier.onInputEvent(event, 0);
+ }
+ onInputEvent(event, msg.arg1);
+ } finally {
+ event.recycle();
+ }
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 26a5b26..f692e05 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -20,6 +20,7 @@ import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.widget.FrameLayout;
+import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -43,20 +44,20 @@ import java.util.HashMap;
*
* <pre>LayoutInflater inflater = (LayoutInflater)context.getSystemService
* (Context.LAYOUT_INFLATER_SERVICE);</pre>
- *
+ *
* <p>
* To create a new LayoutInflater with an additional {@link Factory} for your
* own views, you can use {@link #cloneInContext} to clone an existing
* ViewFactory, and then call {@link #setFactory} on it to include your
* Factory.
- *
+ *
* <p>
* For performance reasons, view inflation relies heavily on pre-processing of
* XML files that is done at build time. Therefore, it is not currently possible
* to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
* it only works with an XmlPullParser returned from a compiled resource
* (R.<em>something</em> file.)
- *
+ *
* @see Context#getSystemService
*/
public abstract class LayoutInflater {
@@ -82,7 +83,7 @@ public abstract class LayoutInflater {
private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
new HashMap<String, Constructor<? extends View>>();
-
+
private HashMap<String, Boolean> mFilterMap;
private static final String TAG_MERGE = "merge";
@@ -93,36 +94,36 @@ public abstract class LayoutInflater {
/**
* Hook to allow clients of the LayoutInflater to restrict the set of Views that are allowed
* to be inflated.
- *
+ *
*/
public interface Filter {
/**
* Hook to allow clients of the LayoutInflater to restrict the set of Views
* that are allowed to be inflated.
- *
+ *
* @param clazz The class object for the View that is about to be inflated
- *
+ *
* @return True if this class is allowed to be inflated, or false otherwise
*/
@SuppressWarnings("unchecked")
boolean onLoadClass(Class clazz);
}
-
+
public interface Factory {
/**
* Hook you can supply that is called when inflating from a LayoutInflater.
* You can use this to customize the tag names available in your XML
* layout files.
- *
+ *
* <p>
* Note that it is good practice to prefix these custom names with your
* package (i.e., com.coolcompany.apps) to avoid conflicts with system
* names.
- *
+ *
* @param name Tag name to be inflated.
* @param context The context the view is being created in.
* @param attrs Inflation attributes as specified in XML file.
- *
+ *
* @return View Newly created view. Return null for the default
* behavior.
*/
@@ -150,14 +151,14 @@ public abstract class LayoutInflater {
private static class FactoryMerger implements Factory2 {
private final Factory mF1, mF2;
private final Factory2 mF12, mF22;
-
+
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
mF1 = f1;
mF2 = f2;
mF12 = f12;
mF22 = f22;
}
-
+
public View onCreateView(String name, Context context, AttributeSet attrs) {
View v = mF1.onCreateView(name, context, attrs);
if (v != null) return v;
@@ -172,13 +173,13 @@ public abstract class LayoutInflater {
: mF2.onCreateView(name, context, attrs);
}
}
-
+
/**
* Create a new LayoutInflater instance associated with a particular Context.
* Applications will almost always want to use
* {@link Context#getSystemService Context.getSystemService()} to retrieve
* the standard {@link Context#LAYOUT_INFLATER_SERVICE Context.INFLATER_SERVICE}.
- *
+ *
* @param context The Context in which this LayoutInflater will create its
* Views; most importantly, this supplies the theme from which the default
* values for their attributes are retrieved.
@@ -191,7 +192,7 @@ public abstract class LayoutInflater {
* Create a new LayoutInflater instance that is a copy of an existing
* LayoutInflater, optionally with its Context changed. For use in
* implementing {@link #cloneInContext}.
- *
+ *
* @param original The original LayoutInflater to copy.
* @param newContext The new Context to use.
*/
@@ -202,7 +203,7 @@ public abstract class LayoutInflater {
mPrivateFactory = original.mPrivateFactory;
mFilter = original.mFilter;
}
-
+
/**
* Obtains the LayoutInflater from the given context.
*/
@@ -220,15 +221,15 @@ public abstract class LayoutInflater {
* pointing to a different Context than the original. This is used by
* {@link ContextThemeWrapper} to create a new LayoutInflater to go along
* with the new Context theme.
- *
+ *
* @param newContext The new Context to associate with the new LayoutInflater.
* May be the same as the original Context if desired.
- *
+ *
* @return Returns a brand spanking new LayoutInflater object associated with
* the given Context.
*/
public abstract LayoutInflater cloneInContext(Context newContext);
-
+
/**
* Return the context we are running in, for access to resources, class
* loader, etc.
@@ -264,7 +265,7 @@ public abstract class LayoutInflater {
* called on each element name as the xml is parsed. If the factory returns
* a View, that is added to the hierarchy. If it returns null, the next
* factory default {@link #onCreateView} method is called.
- *
+ *
* <p>If you have an existing
* LayoutInflater and want to add your own factory to it, use
* {@link #cloneInContext} to clone the existing instance and then you
@@ -320,13 +321,13 @@ public abstract class LayoutInflater {
public Filter getFilter() {
return mFilter;
}
-
+
/**
* Sets the {@link Filter} to by this LayoutInflater. If a view is attempted to be inflated
* which is not allowed by the {@link Filter}, the {@link #inflate(int, ViewGroup)} call will
* throw an {@link InflateException}. This filter will replace any previous filter set on this
* LayoutInflater.
- *
+ *
* @param filter The Filter which restricts the set of Views that are allowed to be inflated.
* This filter will replace any previous filter set on this LayoutInflater.
*/
@@ -340,7 +341,7 @@ public abstract class LayoutInflater {
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
- *
+ *
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy.
@@ -360,7 +361,7 @@ public abstract class LayoutInflater {
* reasons, view inflation relies heavily on pre-processing of XML files
* that is done at build time. Therefore, it is not currently possible to
* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
- *
+ *
* @param parser XML dom node containing the description of the view
* hierarchy.
* @param root Optional view to be the parent of the generated hierarchy.
@@ -375,7 +376,7 @@ public abstract class LayoutInflater {
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
- *
+ *
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy (if
@@ -407,7 +408,7 @@ public abstract class LayoutInflater {
* reasons, view inflation relies heavily on pre-processing of XML files
* that is done at build time. Therefore, it is not currently possible to
* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
- *
+ *
* @param parser XML dom node containing the description of the view
* hierarchy.
* @param root Optional view to be the parent of the generated hierarchy (if
@@ -442,7 +443,7 @@ public abstract class LayoutInflater {
}
final String name = parser.getName();
-
+
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
@@ -528,17 +529,17 @@ public abstract class LayoutInflater {
* Low-level function for instantiating a view by name. This attempts to
* instantiate a view class of the given <var>name</var> found in this
* LayoutInflater's ClassLoader.
- *
+ *
* <p>
* There are two things that can happen in an error case: either the
* exception describing the error will be thrown, or a null will be
* returned. You must deal with both possibilities -- the former will happen
* the first time createView() is called for a class of a particular name,
* the latter every time there-after for that class name.
- *
+ *
* @param name The full name of the class to be instantiated.
* @param attrs The XML attributes supplied for this instance.
- *
+ *
* @return View The newly instantiated view, or null.
*/
public final View createView(String name, String prefix, AttributeSet attrs)
@@ -551,7 +552,7 @@ public abstract class LayoutInflater {
// Class not found in the cache, see if it's real, and try to add it
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
-
+
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
@@ -569,7 +570,7 @@ public abstract class LayoutInflater {
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
-
+
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
@@ -632,10 +633,10 @@ public abstract class LayoutInflater {
* given the xml element name. Override it to handle custom view objects. If
* you override this in your subclass be sure to call through to
* super.onCreateView(name) for names you do not recognize.
- *
+ *
* @param name The fully qualified class name of the View to be create.
* @param attrs An AttributeSet of attributes to apply to the View.
- *
+ *
* @return View The View created.
*/
protected View onCreateView(String name, AttributeSet attrs)
@@ -679,7 +680,7 @@ public abstract class LayoutInflater {
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);
}
-
+
if (view == null) {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
@@ -726,7 +727,7 @@ public abstract class LayoutInflater {
}
final String name = parser.getName();
-
+
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_INCLUDE.equals(name)) {
@@ -741,7 +742,7 @@ public abstract class LayoutInflater {
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs, true);
- viewGroup.addView(view, params);
+ viewGroup.addView(view, params);
} else {
final View view = createViewFromTag(parent, name, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
@@ -810,21 +811,14 @@ public abstract class LayoutInflater {
// We try to load the layout params set in the <include /> tag. If
// they don't exist, we will rely on the layout params set in the
// included XML file.
- // During a layoutparams generation, a runtime exception is thrown
- // if either layout_width or layout_height is missing. We catch
- // this exception and set localParams accordingly: true means we
- // successfully loaded layout params from the <include /> tag,
- // false means we need to rely on the included layout params.
- ViewGroup.LayoutParams params = null;
- try {
- params = group.generateLayoutParams(attrs);
- } catch (RuntimeException e) {
- params = group.generateLayoutParams(childAttrs);
- } finally {
- if (params != null) {
- view.setLayoutParams(params);
- }
- }
+ TypedArray ta = getContext().obtainStyledAttributes(attrs,
+ R.styleable.ViewGroup_Layout);
+ boolean definesBothWidthAndHeight =
+ ta.hasValue(R.styleable.ViewGroup_Layout_layout_width) &&
+ ta.hasValue(R.styleable.ViewGroup_Layout_layout_height);
+ AttributeSet attributes = definesBothWidthAndHeight ? attrs : childAttrs;
+ view.setLayoutParams(group.generateLayoutParams(attributes));
+ ta.recycle();
// Inflate all children.
rInflate(childParser, view, childAttrs, true);
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index bcb8800..4873860 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -17,7 +17,11 @@
package android.view;
import android.content.Context;
+import android.os.SystemClock;
import android.util.FloatMath;
+import android.util.Log;
+
+import java.util.Arrays;
/**
* Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
@@ -137,6 +141,13 @@ public class ScaleGestureDetector {
private long mPrevTime;
private boolean mInProgress;
private int mSpanSlop;
+ private int mMinSpan;
+
+ private float[] mTouchHistoryLastAccepted;
+ private int[] mTouchHistoryDirection;
+ private long[] mTouchHistoryLastAcceptedTime;
+
+ private static final long TOUCH_STABILIZE_TIME = 128; // ms
/**
* Consistency verifier for debugging purposes.
@@ -149,6 +160,135 @@ public class ScaleGestureDetector {
mContext = context;
mListener = listener;
mSpanSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 2;
+ mMinSpan = context.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.config_minScalingSpan);
+ }
+
+ /**
+ * The touchMajor/touchMinor elements of a MotionEvent can flutter/jitter on
+ * some hardware/driver combos. Smooth it out to get kinder, gentler behavior.
+ * @param ev MotionEvent to add to the ongoing history
+ */
+ private void addTouchHistory(MotionEvent ev) {
+ final long currentTime = SystemClock.uptimeMillis();
+ final int count = ev.getPointerCount();
+ for (int i = 0; i < count; i++) {
+ final int id = ev.getPointerId(i);
+ ensureTouchHistorySize(id);
+
+ final boolean hasLastAccepted = !Float.isNaN(mTouchHistoryLastAccepted[id]);
+ boolean accept = true;
+ final int historySize = ev.getHistorySize();
+ for (int h = 0; h < historySize + 1; h++) {
+ final float major;
+ final float minor;
+ if (h < historySize) {
+ major = ev.getHistoricalTouchMajor(i, h);
+ minor = ev.getHistoricalTouchMinor(i, h);
+ } else {
+ major = ev.getTouchMajor(i);
+ minor = ev.getTouchMinor(i);
+ }
+ final float avg = (major + minor) / 2;
+
+ if (hasLastAccepted) {
+ final int directionSig = (int) Math.signum(avg - mTouchHistoryLastAccepted[id]);
+ if (directionSig != mTouchHistoryDirection[id] ||
+ (directionSig == 0 && mTouchHistoryDirection[id] == 0)) {
+ mTouchHistoryDirection[id] = directionSig;
+ final long time = h < historySize ? ev.getHistoricalEventTime(h)
+ : ev.getEventTime();
+ mTouchHistoryLastAcceptedTime[id] = time;
+ accept = false;
+ }
+ if (currentTime - mTouchHistoryLastAcceptedTime[id] < TOUCH_STABILIZE_TIME) {
+ accept = false;
+ }
+ }
+ }
+
+ if (accept) {
+ float newAccepted = (ev.getTouchMajor(i) + ev.getTouchMinor(i)) / 2;
+ if (hasLastAccepted) {
+ newAccepted = (mTouchHistoryLastAccepted[id] + newAccepted) / 2;
+ }
+ mTouchHistoryLastAccepted[id] = newAccepted;
+ mTouchHistoryDirection[id] = 0;
+ mTouchHistoryLastAcceptedTime[id] = ev.getEventTime();
+ }
+ }
+ }
+
+ /**
+ * Clear out the touch history for a given pointer id.
+ * @param id pointer id to clear
+ * @see #addTouchHistory(MotionEvent)
+ */
+ private boolean removeTouchHistoryForId(int id) {
+ if (id >= mTouchHistoryLastAccepted.length) {
+ return false;
+ }
+ mTouchHistoryLastAccepted[id] = Float.NaN;
+ mTouchHistoryDirection[id] = 0;
+ mTouchHistoryLastAcceptedTime[id] = 0;
+ return true;
+ }
+
+ /**
+ * Get the adjusted combined touchMajor/touchMinor value for a given pointer id
+ * @param id the pointer id of the data to obtain
+ * @return the adjusted major/minor value for the point at id
+ * @see #addTouchHistory(MotionEvent)
+ */
+ private float getAdjustedTouchHistory(int id) {
+ if (id >= mTouchHistoryLastAccepted.length) {
+ Log.e(TAG, "Error retrieving adjusted touch history for id=" + id +
+ " - incomplete event stream?");
+ return 0;
+ }
+ return mTouchHistoryLastAccepted[id];
+ }
+
+ /**
+ * Clear all touch history tracking. Useful in ACTION_CANCEL or ACTION_UP.
+ * @see #addTouchHistory(MotionEvent)
+ */
+ private void clearTouchHistory() {
+ if (mTouchHistoryLastAccepted == null) {
+ // All three arrays will be null if this is the case; nothing to do.
+ return;
+ }
+ Arrays.fill(mTouchHistoryLastAccepted, Float.NaN);
+ Arrays.fill(mTouchHistoryDirection, 0);
+ Arrays.fill(mTouchHistoryLastAcceptedTime, 0);
+ }
+
+ private void ensureTouchHistorySize(int id) {
+ final int requiredSize = id + 1;
+ if (mTouchHistoryLastAccepted == null || mTouchHistoryLastAccepted.length < requiredSize) {
+ final float[] newLastAccepted = new float[requiredSize];
+ final int[] newDirection = new int[requiredSize];
+ final long[] newLastAcceptedTime = new long[requiredSize];
+
+ int oldLength = 0;
+ if (mTouchHistoryLastAccepted != null) {
+ System.arraycopy(mTouchHistoryLastAccepted, 0, newLastAccepted, 0,
+ mTouchHistoryLastAccepted.length);
+ System.arraycopy(mTouchHistoryDirection, 0, newDirection, 0,
+ mTouchHistoryDirection.length);
+ System.arraycopy(mTouchHistoryLastAcceptedTime, 0, newLastAcceptedTime, 0,
+ mTouchHistoryLastAcceptedTime.length);
+ oldLength = mTouchHistoryLastAccepted.length;
+ }
+
+ Arrays.fill(newLastAccepted, oldLength, newLastAccepted.length, Float.NaN);
+ Arrays.fill(newDirection, oldLength, newDirection.length, 0);
+ Arrays.fill(newLastAcceptedTime, oldLength, newLastAcceptedTime.length, 0);
+
+ mTouchHistoryLastAccepted = newLastAccepted;
+ mTouchHistoryDirection = newDirection;
+ mTouchHistoryLastAcceptedTime = newLastAcceptedTime;
+ }
}
/**
@@ -183,11 +323,12 @@ public class ScaleGestureDetector {
}
if (streamComplete) {
+ clearTouchHistory();
return true;
}
}
- final boolean configChanged =
+ 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;
@@ -205,12 +346,25 @@ public class ScaleGestureDetector {
final float focusX = sumX / div;
final float focusY = sumY / div;
+ if (pointerUp) {
+ final int id = event.getPointerId(event.getActionIndex());
+ if (!removeTouchHistoryForId(id)) {
+ Log.e(TAG, "Got ACTION_POINTER_UP for previously unknown id=" + id +
+ " - incomplete event stream?");
+ }
+ } else {
+ addTouchHistory(event);
+ }
+
// Determine average deviation from focal point
float devSumX = 0, devSumY = 0;
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
- devSumX += Math.abs(event.getX(i) - focusX);
- devSumY += Math.abs(event.getY(i) - focusY);
+
+ // Average touch major and touch minor and convert the resulting diameter into a radius.
+ final float touchSize = getAdjustedTouchHistory(event.getPointerId(i)) / 2;
+ devSumX += Math.abs(event.getX(i) - focusX) + touchSize;
+ devSumY += Math.abs(event.getY(i) - focusY) + touchSize;
}
final float devX = devSumX / div;
final float devY = devSumY / div;
@@ -228,7 +382,7 @@ public class ScaleGestureDetector {
final boolean wasInProgress = mInProgress;
mFocusX = focusX;
mFocusY = focusY;
- if (mInProgress && (span == 0 || configChanged)) {
+ if (mInProgress && (span < mMinSpan || configChanged)) {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = span;
@@ -238,7 +392,7 @@ public class ScaleGestureDetector {
mPrevSpanY = mCurrSpanY = spanY;
mInitialSpan = mPrevSpan = mCurrSpan = span;
}
- if (!mInProgress && span != 0 &&
+ if (!mInProgress && span >= mMinSpan &&
(wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
mPrevSpanX = mCurrSpanX = spanX;
mPrevSpanY = mCurrSpanY = spanY;
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index a968768..8f4626f 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -16,8 +16,16 @@
package android.view;
+import dalvik.system.CloseGuard;
+
import android.content.res.CompatibilityInfo.Translator;
-import android.graphics.*;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.SurfaceTexture;
+import android.os.IBinder;
import android.os.Parcelable;
import android.os.Parcel;
import android.os.SystemProperties;
@@ -27,212 +35,187 @@ import android.util.Log;
* Handle onto a raw buffer that is being managed by the screen compositor.
*/
public class Surface implements Parcelable {
- private static final String LOG_TAG = "Surface";
- private static final boolean DEBUG_RELEASE = false;
-
- /* orientations for setOrientation() */
- public static final int ROTATION_0 = 0;
- public static final int ROTATION_90 = 1;
- public static final int ROTATION_180 = 2;
- public static final int ROTATION_270 = 3;
+ private static final String TAG = "Surface";
- private static final boolean headless = "1".equals(
+ private static final boolean HEADLESS = "1".equals(
SystemProperties.get("ro.config.headless", "0"));
- private static void checkHeadless() {
- if(headless) {
- throw new UnsupportedOperationException("Device is headless");
+ public static final Parcelable.Creator<Surface> CREATOR =
+ new Parcelable.Creator<Surface>() {
+ public Surface createFromParcel(Parcel source) {
+ try {
+ Surface s = new Surface();
+ s.readFromParcel(source);
+ return s;
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating surface from parcel", e);
+ return null;
+ }
}
- }
-
- /**
- * Create Surface from a {@link SurfaceTexture}.
- *
- * Images drawn to the Surface will be made available to the {@link
- * SurfaceTexture}, which can attach them an OpenGL ES texture via {@link
- * SurfaceTexture#updateTexImage}.
- *
- * @param surfaceTexture The {@link SurfaceTexture} that is updated by this
- * Surface.
- */
- public Surface(SurfaceTexture surfaceTexture) {
- checkHeadless();
- if (DEBUG_RELEASE) {
- mCreationStack = new Exception();
+ public Surface[] newArray(int size) {
+ return new Surface[size];
}
- mCanvas = new CompatibleCanvas();
- initFromSurfaceTexture(surfaceTexture);
- }
+ };
/**
- * Does this object hold a valid surface? Returns true if it holds
- * a physical surface, so lockCanvas() will succeed. Otherwise
- * returns false.
+ * Rotation constant: 0 degree rotation (natural orientation)
*/
- public native boolean isValid();
+ public static final int ROTATION_0 = 0;
- /** Release the local reference to the server-side surface.
- * Always call release() when you're done with a Surface. This will
- * make the surface invalid.
+ /**
+ * Rotation constant: 90 degree rotation.
*/
- public native void release();
-
- /** draw into a surface */
- public Canvas lockCanvas(Rect dirty) throws OutOfResourcesException, IllegalArgumentException {
- /*
- * the dirty rectangle may be expanded to the surface's size, if for
- * instance it has been resized or if the bits were lost, since the last
- * call.
- */
- return lockCanvasNative(dirty);
- }
+ public static final int ROTATION_90 = 1;
- /** unlock the surface and asks a page flip */
- public native void unlockCanvasAndPost(Canvas canvas);
-
- /**
- * unlock the surface. the screen won't be updated until
- * post() or postAll() is called
+ /**
+ * Rotation constant: 180 degree rotation.
*/
- public native void unlockCanvas(Canvas canvas);
-
- @Override
- public String toString() {
- return "Surface(name=" + mName + ", identity=" + getIdentity() + ")";
- }
+ public static final int ROTATION_180 = 2;
- public int describeContents() {
- return 0;
- }
+ /**
+ * Rotation constant: 270 degree rotation.
+ */
+ public static final int ROTATION_270 = 3;
- public native void readFromParcel(Parcel source);
- public native void writeToParcel(Parcel dest, int flags);
+ /* built-in physical display ids (keep in sync with ISurfaceComposer.h)
+ * these are different from the logical display ids used elsewhere in the framework */
/**
- * Exception thrown when a surface couldn't be created or resized
+ * Built-in physical display id: Main display.
+ * Use only with {@link #getBuiltInDisplay()}.
+ * @hide
*/
- public static class OutOfResourcesException extends Exception {
- public OutOfResourcesException() {
- }
- public OutOfResourcesException(String name) {
- super(name);
- }
- }
-
- /*
- * -----------------------------------------------------------------------
- * No user serviceable parts beyond this point
- * -----------------------------------------------------------------------
+ public static final int BUILT_IN_DISPLAY_ID_MAIN = 0;
+
+ /**
+ * Built-in physical display id: Attached HDMI display.
+ * Use only with {@link #getBuiltInDisplay()}.
+ * @hide
*/
+ public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
- /* flags used in constructor (keep in sync with ISurfaceComposer.h) */
+ /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
- /** Surface is created hidden @hide */
- public static final int HIDDEN = 0x00000004;
+ /**
+ * Surface creation flag: Surface is created hidden
+ * @hide */
+ public static final int HIDDEN = 0x00000004;
- /** The surface contains secure content, special measures will
- * be taken to disallow the surface's content to be copied from
- * another process. In particular, screenshots and VNC servers will
+ /**
+ * Surface creation flag: The surface contains secure content, special
+ * 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.
- * @hide*/
- public static final int SECURE = 0x00000080;
-
- /** Creates a surface where color components are interpreted as
- * "non pre-multiplied" by their alpha channel. Of course this flag is
- * meaningless for surfaces without an alpha channel. By default
- * surfaces are pre-multiplied, which means that each color component is
- * already multiplied by its alpha value. In this case the blending
- * equation used is:
- *
+ * @hide
+ */
+ public static final int SECURE = 0x00000080;
+
+ /**
+ * Surface creation flag: Creates a surface where color components are interpreted
+ * as "non pre-multiplied" by their alpha channel. Of course this flag is
+ * meaningless for surfaces without an alpha channel. By default
+ * surfaces are pre-multiplied, which means that each color component is
+ * already multiplied by its alpha value. In this case the blending
+ * equation used is:
+ *
* DEST = SRC + DEST * (1-SRC_ALPHA)
- *
- * By contrast, non pre-multiplied surfaces use the following equation:
- *
+ *
+ * By contrast, non pre-multiplied surfaces use the following equation:
+ *
* DEST = SRC * SRC_ALPHA * DEST * (1-SRC_ALPHA)
- *
- * pre-multiplied surfaces must always be used if transparent pixels are
- * composited on top of each-other into the surface. A pre-multiplied
- * surface can never lower the value of the alpha component of a given
- * pixel.
- *
- * In some rare situations, a non pre-multiplied surface is preferable.
- *
- * @hide
- */
- public static final int NON_PREMULTIPLIED = 0x00000100;
-
+ *
+ * pre-multiplied surfaces must always be used if transparent pixels are
+ * composited on top of each-other into the surface. A pre-multiplied
+ * surface can never lower the value of the alpha component of a given
+ * pixel.
+ *
+ * In some rare situations, a non pre-multiplied surface is preferable.
+ * @hide
+ */
+ public static final int NON_PREMULTIPLIED = 0x00000100;
+
/**
- * Indicates that the surface must be considered opaque, even if its
- * pixel format is set to translucent. This can be useful if an
+ * Surface creation flag: Indicates that the surface must be considered opaque,
+ * even if its pixel format is set to translucent. This can be useful if an
* application needs full RGBA 8888 support for instance but will
* still draw every pixel opaque.
- *
* @hide
*/
- public static final int OPAQUE = 0x00000400;
-
+ public static final int OPAQUE = 0x00000400;
+
/**
- * Application requires a hardware-protected path to an
+ * Surface creation flag: Application requires a hardware-protected path to an
* external display sink. If a hardware-protected path is not available,
* then this surface will not be displayed on the external sink.
- *
* @hide
*/
- public static final int PROTECTED_APP = 0x00000800;
+ public static final int PROTECTED_APP = 0x00000800;
// 0x1000 is reserved for an independent DRM protected flag in framework
- /** Creates a normal surface. This is the default. @hide */
+ /**
+ * Surface creation flag: Creates a normal surface.
+ * This is the default.
+ * @hide
+ */
public static final int FX_SURFACE_NORMAL = 0x00000000;
-
- /** Creates a Blur surface. Everything behind this surface is blurred
- * by some amount. The quality and refresh speed of the blur effect
- * is not settable or guaranteed.
- * It is an error to lock a Blur surface, since it doesn't have
- * a backing store.
+
+ /**
+ * Surface creation flag: Creates a Blur surface.
+ * Everything behind this surface is blurred by some amount.
+ * The quality and refresh speed of the blur effect is not settable or guaranteed.
+ * It is an error to lock a Blur surface, since it doesn't have a backing store.
* @hide
* @deprecated
*/
@Deprecated
- public static final int FX_SURFACE_BLUR = 0x00010000;
-
- /** Creates a Dim surface. Everything behind this surface is dimmed
- * by the amount specified in {@link #setAlpha}.
- * It is an error to lock a Dim surface, since it doesn't have
- * a backing store.
+ public static final int FX_SURFACE_BLUR = 0x00010000;
+
+ /**
+ * Surface creation flag: Creates a Dim surface.
+ * Everything behind this surface is dimmed by the amount specified
+ * in {@link #setAlpha}. It is an error to lock a Dim surface, since it
+ * doesn't have a backing store.
* @hide
*/
- public static final int FX_SURFACE_DIM = 0x00020000;
+ public static final int FX_SURFACE_DIM = 0x00020000;
- /** @hide */
- public static final int FX_SURFACE_SCREENSHOT = 0x00030000;
+ /**
+ * @hide
+ */
+ public static final int FX_SURFACE_SCREENSHOT = 0x00030000;
- /** Mask used for FX values above @hide */
- public static final int FX_SURFACE_MASK = 0x000F0000;
+ /**
+ * Mask used for FX values above.
+ * @hide
+ */
+ public static final int FX_SURFACE_MASK = 0x000F0000;
/* flags used with setFlags() (keep in sync with ISurfaceComposer.h) */
- /** Hide the surface. Equivalent to calling hide(). @hide */
- public static final int SURFACE_HIDDEN = 0x01;
-
- /** Freeze the surface. Equivalent to calling freeze(). @hide */
- public static final int SURFACE_FROZEN = 0x02;
+ /**
+ * Surface flag: Hide the surface.
+ * Equivalent to calling hide().
+ * @hide
+ */
+ public static final int SURFACE_HIDDEN = 0x01;
+
- /** Enable dithering when compositing this surface @hide */
- public static final int SURFACE_DITHER = 0x04;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private String mName;
+ // Note: These fields are accessed by native code.
// The mSurfaceControl will only be present for Surfaces used by the window
// server or system processes. When this class is parceled we defer to the
// mSurfaceControl to do the parceling. Otherwise we parcel the
// mNativeSurface.
- private int mSurfaceControl;
- private int mSaveCount;
- private Canvas mCanvas;
- private int mNativeSurface;
- private int mSurfaceGenerationId;
- private String mName;
+ private int mNativeSurface; // Surface*
+ private int mNativeSurfaceControl; // SurfaceControl*
+ private int mGenerationId; // incremented each time mNativeSurface changes
+ private final Canvas mCanvas = new CompatibleCanvas();
+ private int mCanvasSaveCount; // Canvas save count at time of lockCanvas()
// The Translator for density compatibility mode. This is used for scaling
// the canvas to perform the appropriate density transformation.
@@ -242,154 +225,241 @@ public class Surface implements Parcelable {
// non compatibility mode.
private Matrix mCompatibleMatrix;
- private Exception mCreationStack;
+ private native void nativeCreate(SurfaceSession session, String name,
+ int w, int h, int format, int flags)
+ throws OutOfResourcesException;
+ private native void nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture)
+ throws OutOfResourcesException;
+ private native void nativeRelease();
+ private native void nativeDestroy();
+
+ private native boolean nativeIsValid();
+ private native int nativeGetIdentity();
+ private native boolean nativeIsConsumerRunningBehind();
+
+ private native Canvas nativeLockCanvas(Rect dirty);
+ private native void nativeUnlockCanvasAndPost(Canvas canvas);
+
+ private static native Bitmap nativeScreenshot(IBinder displayToken,
+ int width, int height, int minLayer, int maxLayer, boolean allLayers);
+
+ private static native void nativeOpenTransaction();
+ private static native void nativeCloseTransaction();
+
+ private native void nativeSetLayer(int zorder);
+ private native void nativeSetPosition(float x, float y);
+ private native void nativeSetSize(int w, int h);
+ private native void nativeSetTransparentRegionHint(Region region);
+ private native void nativeSetAlpha(float alpha);
+ private native void nativeSetMatrix(float dsdx, float dtdx, float dsdy, float dtdy);
+ private native void nativeSetFlags(int flags, int mask);
+ private native void nativeSetWindowCrop(Rect crop);
+ private native void nativeSetLayerStack(int layerStack);
+
+ private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
+ private static native IBinder nativeCreateDisplay(String name);
+ private static native void nativeSetDisplaySurface(
+ IBinder displayToken, Surface surface);
+ private static native void nativeSetDisplayLayerStack(
+ IBinder displayToken, int layerStack);
+ private static native void nativeSetDisplayProjection(
+ IBinder displayToken, int orientation, Rect layerStackRect, Rect displayRect);
+ private static native boolean nativeGetDisplayInfo(
+ IBinder displayToken, PhysicalDisplayInfo outInfo);
+
+ private native void nativeCopyFrom(Surface other);
+ private native void nativeTransferFrom(Surface other);
+ private native void nativeReadFromParcel(Parcel source);
+ private native void nativeWriteToParcel(Parcel dest);
- /*
- * We use a class initializer to allow the native code to cache some
- * field offsets.
+ /**
+ * Create an empty surface, which will later be filled in by readFromParcel().
+ * @hide
*/
- native private static void nativeClassInit();
- static { nativeClassInit(); }
-
- /** create a surface @hide */
- public Surface(SurfaceSession s,
- int pid, int display, int w, int h, int format, int flags)
- throws OutOfResourcesException {
+ public Surface() {
checkHeadless();
- if (DEBUG_RELEASE) {
- mCreationStack = new Exception();
- }
- mCanvas = new CompatibleCanvas();
- init(s,pid,null,display,w,h,format,flags);
+ mCloseGuard.open("release");
}
- /** create a surface with a name @hide */
- public Surface(SurfaceSession s,
- int pid, String name, int display, int w, int h, int format, int flags)
- throws OutOfResourcesException {
- checkHeadless();
+ /**
+ * Create a surface with a name.
+ *
+ * The surface creation flags specify what kind of surface to create and
+ * certain options such as whether the surface can be assumed to be opaque
+ * and whether it should be initially hidden. Surfaces should always be
+ * created with the {@link #HIDDEN} flag set to ensure that they are not
+ * made visible prematurely before all of the surface's properties have been
+ * configured.
+ *
+ * Good practice is to first create the surface with the {@link #HIDDEN} flag
+ * specified, open a transaction, set the surface layer, layer stack, alpha,
+ * and position, call {@link #show} if appropriate, and close the transaction.
+ *
+ * @param session The surface session, must not be null.
+ * @param name The surface name, must not be null.
+ * @param w The surface initial width.
+ * @param h The surface initial height.
+ * @param flags The surface creation flags. Should always include {@link #HIDDEN}
+ * in the creation flags.
+ * @hide
+ */
+ public Surface(SurfaceSession session,
+ String name, int w, int h, int format, int flags)
+ throws OutOfResourcesException {
+ if (session == null) {
+ throw new IllegalArgumentException("session must not be null");
+ }
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
- if (DEBUG_RELEASE) {
- mCreationStack = new Exception();
+ if ((flags & HIDDEN) == 0) {
+ Log.w(TAG, "Surfaces should always be created with the HIDDEN flag set "
+ + "to ensure that they are not made visible prematurely before "
+ + "all of the surface's properties have been configured. "
+ + "Set the other properties and make the surface visible within "
+ + "a transaction. New surface name: " + name,
+ new Throwable());
}
- mCanvas = new CompatibleCanvas();
- init(s,pid,name,display,w,h,format,flags);
+
+ checkHeadless();
+
mName = name;
+ nativeCreate(session, name, w, h, format, flags);
+
+ mCloseGuard.open("release");
}
/**
- * Create an empty surface, which will later be filled in by
- * readFromParcel().
- * @hide
+ * Create Surface from a {@link SurfaceTexture}.
+ *
+ * Images drawn to the Surface will be made available to the {@link
+ * SurfaceTexture}, which can attach them to an OpenGL ES texture via {@link
+ * SurfaceTexture#updateTexImage}.
+ *
+ * @param surfaceTexture The {@link SurfaceTexture} that is updated by this
+ * Surface.
*/
- public Surface() {
+ public Surface(SurfaceTexture surfaceTexture) {
+ if (surfaceTexture == null) {
+ throw new IllegalArgumentException("surfaceTexture must not be null");
+ }
+
checkHeadless();
- if (DEBUG_RELEASE) {
- mCreationStack = new Exception();
+ mName = surfaceTexture.toString();
+ try {
+ nativeCreateFromSurfaceTexture(surfaceTexture);
+ } catch (OutOfResourcesException ex) {
+ // We can't throw OutOfResourcesException because it would be an API change.
+ throw new RuntimeException(ex);
}
- mCanvas = new CompatibleCanvas();
+
+ mCloseGuard.open("release");
}
- private Surface(Parcel source) throws OutOfResourcesException {
- init(source);
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ nativeRelease();
+ } finally {
+ super.finalize();
+ }
}
/**
- * Copy another surface to this one. This surface now holds a reference
- * to the same data as the original surface, and is -not- the owner.
- * This is for use by the window manager when returning a window surface
- * back from a client, converting it from the representation being managed
- * by the window manager to the representation the client uses to draw
- * in to it.
+ * Release the local reference to the server-side surface.
+ * Always call release() when you're done with a Surface.
+ * This will make the surface invalid.
+ */
+ public void release() {
+ nativeRelease();
+ mCloseGuard.close();
+ }
+
+ /**
+ * Free all server-side state associated with this surface and
+ * release this object's reference. This method can only be
+ * called from the process that created the service.
* @hide
*/
- public native void copyFrom(Surface o);
+ public void destroy() {
+ nativeDestroy();
+ mCloseGuard.close();
+ }
/**
- * Transfer the native state from 'o' to this surface, releasing it
- * from 'o'. This is for use in the client side for drawing into a
- * surface; not guaranteed to work on the window manager side.
- * This is for use by the client to move the underlying surface from
- * one Surface object to another, in particular in SurfaceFlinger.
- * @hide.
+ * Returns true if this object holds a valid surface.
+ *
+ * @return True if it holds a physical surface, so lockCanvas() will succeed.
+ * Otherwise returns false.
*/
- public native void transferFrom(Surface o);
+ public boolean isValid() {
+ return nativeIsValid();
+ }
- /** @hide */
+ /**
+ * Gets the generation number of this surface, incremented each time
+ * the native surface contained within this object changes.
+ *
+ * @return The current generation number.
+ * @hide
+ */
public int getGenerationId() {
- return mSurfaceGenerationId;
+ return mGenerationId;
}
-
/**
- * Whether the consumer of this Surface is running behind the producer;
- * that is, isConsumerRunningBehind() returns true if the consumer is more
- * than one buffer ahead of the producer.
+ * Returns true if the consumer of this Surface is running behind the producer.
+ *
+ * @return True if the consumer is more than one buffer ahead of the producer.
* @hide
*/
- public native boolean isConsumerRunningBehind();
+ public boolean isConsumerRunningBehind() {
+ return nativeIsConsumerRunningBehind();
+ }
/**
- * A Canvas class that can handle the compatibility mode. This does two
- * things differently.
- * <ul>
- * <li>Returns the width and height of the target metrics, rather than
- * native. For example, the canvas returns 320x480 even if an app is running
- * in WVGA high density.
- * <li>Scales the matrix in setMatrix by the application scale, except if
- * the matrix looks like obtained from getMatrix. This is a hack to handle
- * the case that an application uses getMatrix to keep the original matrix,
- * set matrix of its own, then set the original matrix back. There is no
- * perfect solution that works for all cases, and there are a lot of cases
- * that this model does not work, but we hope this works for many apps.
- * </ul>
+ * Gets a {@link Canvas} for drawing into this surface.
+ *
+ * After drawing into the provided {@link Canvas}, the caller should
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * @param dirty A rectangle that represents the dirty region that the caller wants
+ * to redraw. This function may choose to expand the dirty rectangle if for example
+ * the surface has been resized or if the previous contents of the surface were
+ * not available. The caller should redraw the entire dirty region as represented
+ * by the contents of the dirty rect upon return from this function.
+ * The caller may also pass <code>null</code> instead, in the case where the
+ * entire surface should be redrawn.
+ * @return A canvas for drawing into the surface.
*/
- private class CompatibleCanvas extends Canvas {
- // A temp matrix to remember what an application obtained via {@link getMatrix}
- private Matrix mOrigMatrix = null;
-
- @Override
- public int getWidth() {
- int w = super.getWidth();
- if (mCompatibilityTranslator != null) {
- w = (int)(w * mCompatibilityTranslator.applicationInvertedScale + .5f);
- }
- return w;
- }
-
- @Override
- public int getHeight() {
- int h = super.getHeight();
- if (mCompatibilityTranslator != null) {
- h = (int)(h * mCompatibilityTranslator.applicationInvertedScale + .5f);
- }
- return h;
- }
+ public Canvas lockCanvas(Rect dirty)
+ throws OutOfResourcesException, IllegalArgumentException {
+ return nativeLockCanvas(dirty);
+ }
- @Override
- public void setMatrix(Matrix matrix) {
- if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) {
- // don't scale the matrix if it's not compatibility mode, or
- // the matrix was obtained from getMatrix.
- super.setMatrix(matrix);
- } else {
- Matrix m = new Matrix(mCompatibleMatrix);
- m.preConcat(matrix);
- super.setMatrix(m);
- }
- }
+ /**
+ * Posts the new contents of the {@link Canvas} to the surface and
+ * releases the {@link Canvas}.
+ *
+ * @param canvas The canvas previously obtained from {@link #lockCanvas}.
+ */
+ public void unlockCanvasAndPost(Canvas canvas) {
+ nativeUnlockCanvasAndPost(canvas);
+ }
- @Override
- public void getMatrix(Matrix m) {
- super.getMatrix(m);
- if (mOrigMatrix == null) {
- mOrigMatrix = new Matrix();
- }
- mOrigMatrix.set(m);
- }
+ /**
+ * @deprecated This API has been removed and is not supported. Do not use.
+ */
+ @Deprecated
+ public void unlockCanvas(Canvas canvas) {
+ throw new UnsupportedOperationException();
}
/**
@@ -403,60 +473,19 @@ public class Surface implements Parcelable {
mCompatibleMatrix.setScale(appScale, appScale);
}
}
-
- /** Free all server-side state associated with this surface and
- * release this object's reference. @hide */
- public native void destroy();
-
- private native Canvas lockCanvasNative(Rect dirty);
-
- /*
- * set display parameters & screenshots
- */
-
- /**
- * Freezes the specified display, No updating of the screen will occur
- * until unfreezeDisplay() is called. Everything else works as usual though,
- * in particular transactions.
- * @param display
- * @hide
- */
- public static native void freezeDisplay(int display);
-
- /**
- * resume updating the specified display.
- * @param display
- * @hide
- */
- public static native void unfreezeDisplay(int display);
/**
- * set the orientation of the given display.
- * @param display
- * @param orientation
- * @param flags Currently unused, set to 0.
- * @hide
- */
- public static native void setOrientation(int display, int orientation, int flags);
-
- /**
- * set the orientation of the given display.
- * @param display
- * @param orientation
- * @hide
- */
- public static void setOrientation(int display, int orientation) {
- setOrientation(display, orientation, 0);
- }
-
- /**
* Like {@link #screenshot(int, int, int, int)} but includes all
* Surfaces in the screenshot.
*
* @hide
*/
- public static native Bitmap screenshot(int width, int height);
-
+ public static Bitmap screenshot(int width, int height) {
+ // TODO: should take the display as a parameter
+ IBinder displayToken = getBuiltInDisplay(BUILT_IN_DISPLAY_ID_MAIN);
+ return nativeScreenshot(displayToken, width, height, 0, 0, true);
+ }
+
/**
* Copy the current screen contents into a bitmap and return it.
*
@@ -468,95 +497,345 @@ public class Surface implements Parcelable {
* include in the screenshot.
* @param maxLayer The highest (top-most Z order) surface layer to
* include in the screenshot.
- * @return Returns a Bitmap containing the screen contents.
+ * @return Returns a Bitmap containing the screen contents, or null
+ * if an error occurs.
*
* @hide
*/
- public static native Bitmap screenshot(int width, int height, int minLayer, int maxLayer);
+ public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer) {
+ // TODO: should take the display as a parameter
+ IBinder displayToken = getBuiltInDisplay(BUILT_IN_DISPLAY_ID_MAIN);
+ return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false);
+ }
-
/*
* set surface parameters.
* needs to be inside open/closeTransaction block
*/
-
+
/** start a transaction @hide */
- public static native void openTransaction();
+ public static void openTransaction() {
+ nativeOpenTransaction();
+ }
+
/** end a transaction @hide */
- public static native void closeTransaction();
+ public static void closeTransaction() {
+ nativeCloseTransaction();
+ }
+
/** @hide */
- public native void setLayer(int zorder);
+ public void setLayer(int zorder) {
+ nativeSetLayer(zorder);
+ }
+
/** @hide */
- public void setPosition(int x, int y) { setPosition((float)x, (float)y); }
+ public void setPosition(int x, int y) {
+ nativeSetPosition((float)x, (float)y);
+ }
+
/** @hide */
- public native void setPosition(float x, float y);
+ public void setPosition(float x, float y) {
+ nativeSetPosition(x, y);
+ }
+
/** @hide */
- public native void setSize(int w, int h);
+ public void setSize(int w, int h) {
+ nativeSetSize(w, h);
+ }
+
/** @hide */
- public native void hide();
+ public void hide() {
+ nativeSetFlags(SURFACE_HIDDEN, SURFACE_HIDDEN);
+ }
+
/** @hide */
- public native void show();
+ public void show() {
+ nativeSetFlags(0, SURFACE_HIDDEN);
+ }
+
+ /** @hide */
+ public void setTransparentRegionHint(Region region) {
+ nativeSetTransparentRegionHint(region);
+ }
+
+ /** @hide */
+ public void setAlpha(float alpha) {
+ nativeSetAlpha(alpha);
+ }
+
+ /** @hide */
+ public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+ nativeSetMatrix(dsdx, dtdx, dsdy, dtdy);
+ }
+
/** @hide */
- public native void setTransparentRegionHint(Region region);
+ public void setFlags(int flags, int mask) {
+ nativeSetFlags(flags, mask);
+ }
+
/** @hide */
- public native void setAlpha(float alpha);
+ public void setWindowCrop(Rect crop) {
+ nativeSetWindowCrop(crop);
+ }
+
/** @hide */
- public native void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy);
+ public void setLayerStack(int layerStack) {
+ nativeSetLayerStack(layerStack);
+ }
+
/** @hide */
- public native void freeze();
+ public static IBinder getBuiltInDisplay(int builtInDisplayId) {
+ return nativeGetBuiltInDisplay(builtInDisplayId);
+ }
+
/** @hide */
- public native void unfreeze();
+ public static IBinder createDisplay(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+ return nativeCreateDisplay(name);
+ }
+
/** @hide */
- public native void setFreezeTint(int tint);
+ public static void setDisplaySurface(IBinder displayToken, Surface surface) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeSetDisplaySurface(displayToken, surface);
+ }
+
/** @hide */
- public native void setFlags(int flags, int mask);
+ public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeSetDisplayLayerStack(displayToken, layerStack);
+ }
+
/** @hide */
- public native void setWindowCrop(Rect crop);
+ public static void setDisplayProjection(IBinder displayToken,
+ int orientation, Rect layerStackRect, Rect displayRect) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (layerStackRect == null) {
+ throw new IllegalArgumentException("layerStackRect must not be null");
+ }
+ if (displayRect == null) {
+ throw new IllegalArgumentException("displayRect must not be null");
+ }
+ nativeSetDisplayProjection(displayToken, orientation, layerStackRect, displayRect);
+ }
+ /** @hide */
+ public static boolean getDisplayInfo(IBinder displayToken, PhysicalDisplayInfo outInfo) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (outInfo == null) {
+ throw new IllegalArgumentException("outInfo must not be null");
+ }
+ return nativeGetDisplayInfo(displayToken, outInfo);
+ }
-
- public static final Parcelable.Creator<Surface> CREATOR
- = new Parcelable.Creator<Surface>()
- {
- public Surface createFromParcel(Parcel source) {
- try {
- return new Surface(source);
- } catch (Exception e) {
- Log.e(LOG_TAG, "Exception creating surface from parcel", e);
- }
- return null;
+ /**
+ * Copy another surface to this one. This surface now holds a reference
+ * to the same data as the original surface, and is -not- the owner.
+ * This is for use by the window manager when returning a window surface
+ * back from a client, converting it from the representation being managed
+ * by the window manager to the representation the client uses to draw
+ * in to it.
+ * @hide
+ */
+ public void copyFrom(Surface other) {
+ if (other == null) {
+ throw new IllegalArgumentException("other must not be null");
}
+ if (other != this) {
+ nativeCopyFrom(other);
+ }
+ }
- public Surface[] newArray(int size) {
- return new Surface[size];
+ /**
+ * Transfer the native state from 'other' to this surface, releasing it
+ * from 'other'. This is for use in the client side for drawing into a
+ * surface; not guaranteed to work on the window manager side.
+ * This is for use by the client to move the underlying surface from
+ * one Surface object to another, in particular in SurfaceFlinger.
+ * @hide.
+ */
+ public void transferFrom(Surface other) {
+ if (other == null) {
+ throw new IllegalArgumentException("other must not be null");
}
- };
+ if (other != this) {
+ nativeTransferFrom(other);
+ }
+ }
@Override
- protected void finalize() throws Throwable {
- try {
- super.finalize();
- } finally {
- if (mNativeSurface != 0 || mSurfaceControl != 0) {
- if (DEBUG_RELEASE) {
- Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() ("
- + mNativeSurface + ", " + mSurfaceControl + ")", mCreationStack);
- } else {
- Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() ("
- + mNativeSurface + ", " + mSurfaceControl + ")");
- }
- }
- release();
+ public int describeContents() {
+ return 0;
+ }
+
+ public void readFromParcel(Parcel source) {
+ if (source == null) {
+ throw new IllegalArgumentException("source must not be null");
+ }
+
+ mName = source.readString();
+ nativeReadFromParcel(source);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (dest == null) {
+ throw new IllegalArgumentException("dest must not be null");
+ }
+
+ dest.writeString(mName);
+ nativeWriteToParcel(dest);
+ if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+ release();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Surface(name=" + mName + ", identity=" + nativeGetIdentity() + ")";
+ }
+
+ private static void checkHeadless() {
+ if (HEADLESS) {
+ throw new UnsupportedOperationException("Device is headless");
+ }
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ /**
+ * Describes the properties of a physical display known to surface flinger.
+ * @hide
+ */
+ public static final class PhysicalDisplayInfo {
+ public int width;
+ public int height;
+ public float refreshRate;
+ public float density;
+ public float xDpi;
+ public float yDpi;
+
+ 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
+ && height == other.height
+ && refreshRate == other.refreshRate
+ && density == other.density
+ && xDpi == other.xDpi
+ && yDpi == other.yDpi;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0; // don't care
+ }
+
+ public void copyFrom(PhysicalDisplayInfo other) {
+ width = other.width;
+ height = other.height;
+ refreshRate = other.refreshRate;
+ density = other.density;
+ xDpi = other.xDpi;
+ yDpi = other.yDpi;
+ }
+
+ // For debugging purposes
+ @Override
+ public String toString() {
+ return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, "
+ + "density " + density + ", " + xDpi + " x " + yDpi + " dpi}";
}
}
-
- private native void init(SurfaceSession s,
- int pid, String name, int display, int w, int h, int format, int flags)
- throws OutOfResourcesException;
- private native void init(Parcel source);
+ /**
+ * A Canvas class that can handle the compatibility mode.
+ * This does two things differently.
+ * <ul>
+ * <li>Returns the width and height of the target metrics, rather than
+ * native. For example, the canvas returns 320x480 even if an app is running
+ * in WVGA high density.
+ * <li>Scales the matrix in setMatrix by the application scale, except if
+ * the matrix looks like obtained from getMatrix. This is a hack to handle
+ * the case that an application uses getMatrix to keep the original matrix,
+ * set matrix of its own, then set the original matrix back. There is no
+ * perfect solution that works for all cases, and there are a lot of cases
+ * that this model does not work, but we hope this works for many apps.
+ * </ul>
+ */
+ private final class CompatibleCanvas extends Canvas {
+ // A temp matrix to remember what an application obtained via {@link getMatrix}
+ private Matrix mOrigMatrix = null;
- private native void initFromSurfaceTexture(SurfaceTexture surfaceTexture);
+ @Override
+ public int getWidth() {
+ int w = super.getWidth();
+ if (mCompatibilityTranslator != null) {
+ w = (int)(w * mCompatibilityTranslator.applicationInvertedScale + .5f);
+ }
+ return w;
+ }
- private native int getIdentity();
+ @Override
+ public int getHeight() {
+ int h = super.getHeight();
+ if (mCompatibilityTranslator != null) {
+ h = (int)(h * mCompatibilityTranslator.applicationInvertedScale + .5f);
+ }
+ return h;
+ }
+
+ @Override
+ public void setMatrix(Matrix matrix) {
+ if (mCompatibleMatrix == null || mOrigMatrix == null || mOrigMatrix.equals(matrix)) {
+ // don't scale the matrix if it's not compatibility mode, or
+ // the matrix was obtained from getMatrix.
+ super.setMatrix(matrix);
+ } else {
+ Matrix m = new Matrix(mCompatibleMatrix);
+ m.preConcat(matrix);
+ super.setMatrix(m);
+ }
+ }
+
+ @Override
+ public void getMatrix(Matrix m) {
+ super.getMatrix(m);
+ if (mOrigMatrix == null) {
+ mOrigMatrix = new Matrix();
+ }
+ mOrigMatrix.set(m);
+ }
+ }
}
diff --git a/core/java/android/view/SurfaceSession.java b/core/java/android/view/SurfaceSession.java
index 2a04675..0dfd94a 100644
--- a/core/java/android/view/SurfaceSession.java
+++ b/core/java/android/view/SurfaceSession.java
@@ -16,34 +16,44 @@
package android.view;
-
/**
* An instance of this class represents a connection to the surface
- * flinger, in which you can create one or more Surface instances that will
+ * flinger, from which you can create one or more Surface instances that will
* be composited to the screen.
* {@hide}
*/
-public class SurfaceSession {
+public final class SurfaceSession {
+ // Note: This field is accessed by native code.
+ private int mNativeClient; // SurfaceComposerClient*
+
+ private static native int nativeCreate();
+ private static native void nativeDestroy(int ptr);
+ private static native void nativeKill(int ptr);
+
/** Create a new connection with the surface flinger. */
public SurfaceSession() {
- init();
+ mNativeClient = nativeCreate();
}
- /** Forcibly detach native resources associated with this object.
- * Unlike destroy(), after this call any surfaces that were created
- * from the session will no longer work. The session itself is destroyed.
- */
- public native void kill();
-
/* no user serviceable parts here ... */
@Override
protected void finalize() throws Throwable {
- destroy();
+ try {
+ if (mNativeClient != 0) {
+ nativeDestroy(mNativeClient);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Forcibly detach native resources associated with this object.
+ * Unlike destroy(), after this call any surfaces that were created
+ * from the session will no longer work.
+ */
+ public void kill() {
+ nativeKill(mNativeClient);
}
-
- private native void init();
- private native void destroy();
-
- private int mClient;
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index ed4c75c..0d16dd3 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -46,13 +46,18 @@ import java.util.concurrent.locks.ReentrantLock;
*
* <p>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
+ * surface to be displayed. The view hierarchy will take care of correctly
* compositing with the Surface any siblings of the SurfaceView that would
- * normally appear on top of it. This can be used to place overlays such as
+ * normally appear on top of it. This can be used to place overlays such as
* 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.
*
+ * <p> 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
+ * view may not be properly composited with the surface.
+ *
* <p>Access to the underlying surface is provided via the SurfaceHolder interface,
* which can be retrieved by calling {@link #getHolder}.
*
@@ -62,14 +67,14 @@ import java.util.concurrent.locks.ReentrantLock;
* Surface is created and destroyed as the window is shown and hidden.
*
* <p>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
+ * 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:
*
* <ul>
* <li> All SurfaceView and
* {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
* from the thread running the SurfaceView's window (typically the main thread
- * of the application). They thus need to correctly synchronize with any
+ * of the application). They thus need to correctly synchronize with any
* state that is also touched by the drawing thread.
* <li> You must ensure that the drawing thread only touches the underlying
* Surface while it is valid -- between
@@ -296,7 +301,7 @@ public class SurfaceView extends View {
}
boolean opaque = true;
- if ((mPrivateFlags & SKIP_DRAW) == 0) {
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
// this view draws, remove it from the transparent region
opaque = super.gatherTransparentRegion(region);
} else if (region != null) {
@@ -320,7 +325,7 @@ public class SurfaceView extends View {
public void draw(Canvas canvas) {
if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
// draw() is not called when SKIP_DRAW is set
- if ((mPrivateFlags & SKIP_DRAW) == 0) {
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
// punch a whole in the view-hierarchy below us
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
@@ -332,7 +337,7 @@ public class SurfaceView extends View {
protected void dispatchDraw(Canvas canvas) {
if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
// if SKIP_DRAW is cleared, draw() has already punched a hole
- if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
// punch a whole in the view-hierarchy below us
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
@@ -456,11 +461,12 @@ public class SurfaceView extends View {
}
if (mWindow == null) {
+ Display display = getDisplay();
mWindow = new MyWindow(this);
mLayout.type = mWindowType;
- mLayout.gravity = Gravity.LEFT|Gravity.TOP;
- mSession.addWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,
- mVisible ? VISIBLE : GONE, mContentInsets);
+ mLayout.gravity = Gravity.START|Gravity.TOP;
+ mSession.addToDisplayWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,
+ mVisible ? VISIBLE : GONE, display.getDisplayId(), mContentInsets);
}
boolean realSizeChanged;
@@ -480,10 +486,10 @@ public class SurfaceView extends View {
relayoutResult = mSession.relayout(
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
visible ? VISIBLE : GONE,
- WindowManagerImpl.RELAYOUT_DEFER_SURFACE_DESTROY,
+ WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
mWinFrame, mContentInsets,
mVisibleInsets, mConfiguration, mNewSurface);
- if ((relayoutResult&WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) {
+ if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mReportDrawNeeded = true;
}
@@ -516,8 +522,8 @@ public class SurfaceView extends View {
SurfaceHolder.Callback callbacks[] = null;
- final boolean surfaceChanged =
- (relayoutResult&WindowManagerImpl.RELAYOUT_RES_SURFACE_CHANGED) != 0;
+ final boolean surfaceChanged = (relayoutResult
+ & WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED) != 0;
if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
mSurfaceCreated = false;
if (mSurface.isValid()) {
@@ -615,21 +621,22 @@ public class SurfaceView extends View {
mSurfaceView = new WeakReference<SurfaceView>(surfaceView);
}
- public void resized(int w, int h, Rect contentInsets,
+ @Override
+ public void resized(Rect frame, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
SurfaceView surfaceView = mSurfaceView.get();
if (surfaceView != null) {
if (DEBUG) Log.v(
- "SurfaceView", surfaceView + " got resized: w=" +
- w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight);
+ "SurfaceView", surfaceView + " got resized: w=" + frame.width()
+ + " h=" + frame.height() + ", cur w=" + mCurWidth + " h=" + mCurHeight);
surfaceView.mSurfaceLock.lock();
try {
if (reportDraw) {
surfaceView.mUpdateWindowNeeded = true;
surfaceView.mReportDrawNeeded = true;
surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG);
- } else if (surfaceView.mWinFrame.width() != w
- || surfaceView.mWinFrame.height() != h) {
+ } else if (surfaceView.mWinFrame.width() != frame.width()
+ || surfaceView.mWinFrame.height() != frame.height()) {
surfaceView.mUpdateWindowNeeded = true;
surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG);
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index a719a01..876b7d84 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -224,6 +224,7 @@ public class TextureView extends View {
private void destroySurface() {
if (mLayer != null) {
mSurface.detachFromGLContext();
+ mLayer.clearStorage();
boolean shouldRelease = true;
if (mListener != null) {
@@ -367,6 +368,7 @@ public class TextureView extends View {
if (mListener != null && !mUpdateSurface) {
mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());
}
+ mLayer.setLayerPaint(mLayerPaint);
}
if (mUpdateSurface) {
@@ -534,7 +536,8 @@ public class TextureView extends View {
*/
public Bitmap getBitmap(int width, int height) {
if (isAvailable() && width > 0 && height > 0) {
- return getBitmap(Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888));
+ return getBitmap(Bitmap.createBitmap(getResources().getDisplayMetrics(),
+ width, height, Bitmap.Config.ARGB_8888));
}
return null;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f2a80d0..3ed47ea 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -39,6 +39,7 @@ import android.graphics.Region;
import android.graphics.Shader;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -47,9 +48,9 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.FloatProperty;
-import android.util.LocaleUtil;
import android.util.Log;
import android.util.Pool;
import android.util.Poolable;
@@ -82,14 +83,21 @@ import static java.lang.Math.max;
import com.android.internal.R;
import com.android.internal.util.Predicate;
import com.android.internal.view.menu.MenuBuilder;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* <p>
@@ -343,9 +351,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
* Padding can be used to offset the content of the view by a specific amount of
* pixels. For instance, a left padding of 2 will push the view's content by
* 2 pixels to the right of the left edge. Padding can be set using the
- * {@link #setPadding(int, int, int, int)} method and queried by calling
- * {@link #getPaddingLeft()}, {@link #getPaddingTop()}, {@link #getPaddingRight()},
- * {@link #getPaddingBottom()}.
+ * {@link #setPadding(int, int, int, int)} or {@link #setPaddingRelative(int, int, int, int)}
+ * method and queried by calling {@link #getPaddingLeft()}, {@link #getPaddingTop()},
+ * {@link #getPaddingRight()}, {@link #getPaddingBottom()}, {@link #getPaddingStart()},
+ * {@link #getPaddingEnd()}.
* </p>
*
* <p>
@@ -627,6 +636,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
* @attr ref android.R.styleable#View_paddingLeft
* @attr ref android.R.styleable#View_paddingRight
* @attr ref android.R.styleable#View_paddingTop
+ * @attr ref android.R.styleable#View_paddingStart
+ * @attr ref android.R.styleable#View_paddingEnd
* @attr ref android.R.styleable#View_saveEnabled
* @attr ref android.R.styleable#View_rotation
* @attr ref android.R.styleable#View_rotationX
@@ -648,6 +659,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
* @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack
* @attr ref android.R.styleable#View_soundEffectsEnabled
* @attr ref android.R.styleable#View_tag
+ * @attr ref android.R.styleable#View_textAlignment
* @attr ref android.R.styleable#View_transformPivotX
* @attr ref android.R.styleable#View_transformPivotY
* @attr ref android.R.styleable#View_translationX
@@ -656,7 +668,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
*
* @see android.view.ViewGroup
*/
-public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback,
+public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
private static final boolean DBG = false;
@@ -1003,14 +1015,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
public static final int FOCUSABLES_TOUCH_MODE = 0x00000001;
/**
- * View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
- * should add only accessibility focusable Views.
- *
- * @hide
- */
- public static final int FOCUSABLES_ACCESSIBILITY = 0x00000002;
-
- /**
* Use with {@link #focusSearch(int)}. Move focus to the previous selectable
* item.
*/
@@ -1042,58 +1046,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public static final int FOCUS_DOWN = 0x00000082;
- // Accessibility focus directions.
-
- /**
- * The accessibility focus which is the current user position when
- * interacting with the accessibility framework.
- *
- * @hide
- */
- public static final int FOCUS_ACCESSIBILITY = 0x00001000;
-
- /**
- * Use with {@link #focusSearch(int)}. Move acessibility focus left.
- *
- * @hide
- */
- public static final int ACCESSIBILITY_FOCUS_LEFT = FOCUS_LEFT | FOCUS_ACCESSIBILITY;
-
- /**
- * Use with {@link #focusSearch(int)}. Move acessibility focus up.
- *
- * @hide
- */
- public static final int ACCESSIBILITY_FOCUS_UP = FOCUS_UP | FOCUS_ACCESSIBILITY;
-
- /**
- * Use with {@link #focusSearch(int)}. Move acessibility focus right.
- *
- * @hide
- */
- public static final int ACCESSIBILITY_FOCUS_RIGHT = FOCUS_RIGHT | FOCUS_ACCESSIBILITY;
-
- /**
- * Use with {@link #focusSearch(int)}. Move acessibility focus down.
- *
- * @hide
- */
- public static final int ACCESSIBILITY_FOCUS_DOWN = FOCUS_DOWN | FOCUS_ACCESSIBILITY;
-
- /**
- * Use with {@link #focusSearch(int)}. Move acessibility focus forward.
- *
- * @hide
- */
- public static final int ACCESSIBILITY_FOCUS_FORWARD = FOCUS_FORWARD | FOCUS_ACCESSIBILITY;
-
- /**
- * Use with {@link #focusSearch(int)}. Move acessibility focus backward.
- *
- * @hide
- */
- public static final int ACCESSIBILITY_FOCUS_BACKWARD = FOCUS_BACKWARD | FOCUS_ACCESSIBILITY;
-
/**
* Bits of {@link #getMeasuredWidthAndState()} and
* {@link #getMeasuredWidthAndState()} that provide the actual measured size.
@@ -1557,7 +1509,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
private SparseArray<Object> mKeyedTags;
/**
- * The next available accessiiblity id.
+ * The next available accessibility id.
*/
private static int sNextAccessibilityViewId;
@@ -1623,17 +1575,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// for mPrivateFlags:
/** {@hide} */
- static final int WANTS_FOCUS = 0x00000001;
+ static final int PFLAG_WANTS_FOCUS = 0x00000001;
/** {@hide} */
- static final int FOCUSED = 0x00000002;
+ static final int PFLAG_FOCUSED = 0x00000002;
/** {@hide} */
- static final int SELECTED = 0x00000004;
+ static final int PFLAG_SELECTED = 0x00000004;
/** {@hide} */
- static final int IS_ROOT_NAMESPACE = 0x00000008;
+ static final int PFLAG_IS_ROOT_NAMESPACE = 0x00000008;
/** {@hide} */
- static final int HAS_BOUNDS = 0x00000010;
+ static final int PFLAG_HAS_BOUNDS = 0x00000010;
/** {@hide} */
- static final int DRAWN = 0x00000020;
+ static final int PFLAG_DRAWN = 0x00000020;
/**
* When this flag is set, this view is running an animation on behalf of its
* children and should therefore not cancel invalidate requests, even if they
@@ -1641,58 +1593,58 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* {@hide}
*/
- static final int DRAW_ANIMATION = 0x00000040;
+ static final int PFLAG_DRAW_ANIMATION = 0x00000040;
/** {@hide} */
- static final int SKIP_DRAW = 0x00000080;
+ static final int PFLAG_SKIP_DRAW = 0x00000080;
/** {@hide} */
- static final int ONLY_DRAWS_BACKGROUND = 0x00000100;
+ static final int PFLAG_ONLY_DRAWS_BACKGROUND = 0x00000100;
/** {@hide} */
- static final int REQUEST_TRANSPARENT_REGIONS = 0x00000200;
+ static final int PFLAG_REQUEST_TRANSPARENT_REGIONS = 0x00000200;
/** {@hide} */
- static final int DRAWABLE_STATE_DIRTY = 0x00000400;
+ static final int PFLAG_DRAWABLE_STATE_DIRTY = 0x00000400;
/** {@hide} */
- static final int MEASURED_DIMENSION_SET = 0x00000800;
+ static final int PFLAG_MEASURED_DIMENSION_SET = 0x00000800;
/** {@hide} */
- static final int FORCE_LAYOUT = 0x00001000;
+ static final int PFLAG_FORCE_LAYOUT = 0x00001000;
/** {@hide} */
- static final int LAYOUT_REQUIRED = 0x00002000;
+ static final int PFLAG_LAYOUT_REQUIRED = 0x00002000;
- private static final int PRESSED = 0x00004000;
+ private static final int PFLAG_PRESSED = 0x00004000;
/** {@hide} */
- static final int DRAWING_CACHE_VALID = 0x00008000;
+ static final int PFLAG_DRAWING_CACHE_VALID = 0x00008000;
/**
* Flag used to indicate that this view should be drawn once more (and only once
* more) after its animation has completed.
* {@hide}
*/
- static final int ANIMATION_STARTED = 0x00010000;
+ static final int PFLAG_ANIMATION_STARTED = 0x00010000;
- private static final int SAVE_STATE_CALLED = 0x00020000;
+ private static final int PFLAG_SAVE_STATE_CALLED = 0x00020000;
/**
* Indicates that the View returned true when onSetAlpha() was called and that
* the alpha must be restored.
* {@hide}
*/
- static final int ALPHA_SET = 0x00040000;
+ static final int PFLAG_ALPHA_SET = 0x00040000;
/**
* Set by {@link #setScrollContainer(boolean)}.
*/
- static final int SCROLL_CONTAINER = 0x00080000;
+ static final int PFLAG_SCROLL_CONTAINER = 0x00080000;
/**
* Set by {@link #setScrollContainer(boolean)}.
*/
- static final int SCROLL_CONTAINER_ADDED = 0x00100000;
+ static final int PFLAG_SCROLL_CONTAINER_ADDED = 0x00100000;
/**
* View flag indicating whether this view was invalidated (fully or partially.)
*
* @hide
*/
- static final int DIRTY = 0x00200000;
+ static final int PFLAG_DIRTY = 0x00200000;
/**
* View flag indicating whether this view was invalidated by an opaque
@@ -1700,35 +1652,35 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @hide
*/
- static final int DIRTY_OPAQUE = 0x00400000;
+ static final int PFLAG_DIRTY_OPAQUE = 0x00400000;
/**
- * Mask for {@link #DIRTY} and {@link #DIRTY_OPAQUE}.
+ * Mask for {@link #PFLAG_DIRTY} and {@link #PFLAG_DIRTY_OPAQUE}.
*
* @hide
*/
- static final int DIRTY_MASK = 0x00600000;
+ static final int PFLAG_DIRTY_MASK = 0x00600000;
/**
* Indicates whether the background is opaque.
*
* @hide
*/
- static final int OPAQUE_BACKGROUND = 0x00800000;
+ static final int PFLAG_OPAQUE_BACKGROUND = 0x00800000;
/**
* Indicates whether the scrollbars are opaque.
*
* @hide
*/
- static final int OPAQUE_SCROLLBARS = 0x01000000;
+ static final int PFLAG_OPAQUE_SCROLLBARS = 0x01000000;
/**
* Indicates whether the view is opaque.
*
* @hide
*/
- static final int OPAQUE_MASK = 0x01800000;
+ static final int PFLAG_OPAQUE_MASK = 0x01800000;
/**
* Indicates a prepressed state;
@@ -1738,27 +1690,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @hide
*/
- private static final int PREPRESSED = 0x02000000;
+ private static final int PFLAG_PREPRESSED = 0x02000000;
/**
* Indicates whether the view is temporarily detached.
*
* @hide
*/
- static final int CANCEL_NEXT_UP_EVENT = 0x04000000;
+ static final int PFLAG_CANCEL_NEXT_UP_EVENT = 0x04000000;
/**
* Indicates that we should awaken scroll bars once attached
*
* @hide
*/
- private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
+ private static final int PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
/**
* Indicates that the view has received HOVER_ENTER. Cleared on HOVER_EXIT.
* @hide
*/
- private static final int HOVERED = 0x10000000;
+ private static final int PFLAG_HOVERED = 0x10000000;
/**
* Indicates that pivotX or pivotY were explicitly set and we should not assume the center
@@ -1766,10 +1718,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @hide
*/
- private static final int PIVOT_EXPLICITLY_SET = 0x20000000;
+ private static final int PFLAG_PIVOT_EXPLICITLY_SET = 0x20000000;
/** {@hide} */
- static final int ACTIVATED = 0x40000000;
+ static final int PFLAG_ACTIVATED = 0x40000000;
/**
* Indicates that this view was specifically invalidated, not just dirtied because some
@@ -1779,16 +1731,60 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @hide
*/
- static final int INVALIDATED = 0x80000000;
-
- /* Masks for mPrivateFlags2 */
+ static final int PFLAG_INVALIDATED = 0x80000000;
+
+ /**
+ * 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
+ * -------|-------|-------|-------|
+ */
/**
* Indicates that this view has reported that it can accept the current drag's content.
* Cleared when the drag operation concludes.
* @hide
*/
- static final int DRAG_CAN_ACCEPT = 0x00000001;
+ static final int PFLAG2_DRAG_CAN_ACCEPT = 0x00000001;
/**
* Indicates that this view is currently directly under the drag location in a
@@ -1796,33 +1792,29 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* the drag exits the view, or when the drag operation concludes.
* @hide
*/
- static final int DRAG_HOVERED = 0x00000002;
+ static final int PFLAG2_DRAG_HOVERED = 0x00000002;
/**
* Horizontal layout direction of this view is from Left to Right.
* Use with {@link #setLayoutDirection}.
- * @hide
*/
public static final int LAYOUT_DIRECTION_LTR = 0;
/**
* Horizontal layout direction of this view is from Right to Left.
* Use with {@link #setLayoutDirection}.
- * @hide
*/
public static final int LAYOUT_DIRECTION_RTL = 1;
/**
* Horizontal layout direction of this view is inherited from its parent.
* Use with {@link #setLayoutDirection}.
- * @hide
*/
public static final int LAYOUT_DIRECTION_INHERIT = 2;
/**
* Horizontal layout direction of this view is from deduced from the default language
* script for the locale. Use with {@link #setLayoutDirection}.
- * @hide
*/
public static final int LAYOUT_DIRECTION_LOCALE = 3;
@@ -1830,32 +1822,33 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
* @hide
*/
- static final int LAYOUT_DIRECTION_MASK_SHIFT = 2;
+ static final int PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT = 2;
/**
* Mask for use with private flags indicating bits used for horizontal layout direction.
* @hide
*/
- static final int LAYOUT_DIRECTION_MASK = 0x00000003 << LAYOUT_DIRECTION_MASK_SHIFT;
+ static final int PFLAG2_LAYOUT_DIRECTION_MASK = 0x00000003 << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
/**
* Indicates whether the view horizontal layout direction has been resolved and drawn to the
* right-to-left direction.
* @hide
*/
- static final int LAYOUT_DIRECTION_RESOLVED_RTL = 4 << LAYOUT_DIRECTION_MASK_SHIFT;
+ static final int PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL = 4 << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
/**
* Indicates whether the view horizontal layout direction has been resolved.
* @hide
*/
- static final int LAYOUT_DIRECTION_RESOLVED = 8 << LAYOUT_DIRECTION_MASK_SHIFT;
+ static final int PFLAG2_LAYOUT_DIRECTION_RESOLVED = 8 << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
/**
* Mask for use with private flags indicating bits used for resolved horizontal layout direction.
* @hide
*/
- static final int LAYOUT_DIRECTION_RESOLVED_MASK = 0x0000000C << LAYOUT_DIRECTION_MASK_SHIFT;
+ static final int PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK = 0x0000000C
+ << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
/*
* Array of horizontal layout direction flags for mapping attribute "layoutDirection" to correct
@@ -1882,12 +1875,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @hide
*/
- static final int HAS_TRANSIENT_STATE = 0x00000100;
-
+ static final int PFLAG2_HAS_TRANSIENT_STATE = 0x1 << 22;
/**
* Text direction is inherited thru {@link ViewGroup}
- * @hide
*/
public static final int TEXT_DIRECTION_INHERIT = 0;
@@ -1895,7 +1886,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* Text direction is using "first strong algorithm". The first strong directional character
* determines the paragraph direction. If there is no strong directional character, the
* paragraph direction is the view's resolved layout direction.
- * @hide
*/
public static final int TEXT_DIRECTION_FIRST_STRONG = 1;
@@ -1903,89 +1893,86 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* Text direction is using "any-RTL" algorithm. The paragraph direction is RTL if it contains
* any strong RTL character, otherwise it is LTR if it contains any strong LTR characters.
* If there are neither, the paragraph direction is the view's resolved layout direction.
- * @hide
*/
public static final int TEXT_DIRECTION_ANY_RTL = 2;
/**
* Text direction is forced to LTR.
- * @hide
*/
public static final int TEXT_DIRECTION_LTR = 3;
/**
* Text direction is forced to RTL.
- * @hide
*/
public static final int TEXT_DIRECTION_RTL = 4;
/**
* Text direction is coming from the system Locale.
- * @hide
*/
public static final int TEXT_DIRECTION_LOCALE = 5;
/**
* Default text direction is inherited
- * @hide
*/
- protected static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT;
+ public static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT;
/**
* Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED)
* @hide
*/
- static final int TEXT_DIRECTION_MASK_SHIFT = 6;
+ static final int PFLAG2_TEXT_DIRECTION_MASK_SHIFT = 6;
/**
* Mask for use with private flags indicating bits used for text direction.
* @hide
*/
- static final int TEXT_DIRECTION_MASK = 0x00000007 << TEXT_DIRECTION_MASK_SHIFT;
+ static final int PFLAG2_TEXT_DIRECTION_MASK = 0x00000007
+ << PFLAG2_TEXT_DIRECTION_MASK_SHIFT;
/**
* Array of text direction flags for mapping attribute "textDirection" to correct
* flag value.
* @hide
*/
- private static final int[] TEXT_DIRECTION_FLAGS = {
- TEXT_DIRECTION_INHERIT << TEXT_DIRECTION_MASK_SHIFT,
- TEXT_DIRECTION_FIRST_STRONG << TEXT_DIRECTION_MASK_SHIFT,
- TEXT_DIRECTION_ANY_RTL << TEXT_DIRECTION_MASK_SHIFT,
- TEXT_DIRECTION_LTR << TEXT_DIRECTION_MASK_SHIFT,
- TEXT_DIRECTION_RTL << TEXT_DIRECTION_MASK_SHIFT,
- TEXT_DIRECTION_LOCALE << TEXT_DIRECTION_MASK_SHIFT
+ private static final int[] PFLAG2_TEXT_DIRECTION_FLAGS = {
+ TEXT_DIRECTION_INHERIT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+ TEXT_DIRECTION_FIRST_STRONG << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+ TEXT_DIRECTION_ANY_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+ TEXT_DIRECTION_LTR << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+ TEXT_DIRECTION_RTL << PFLAG2_TEXT_DIRECTION_MASK_SHIFT,
+ TEXT_DIRECTION_LOCALE << PFLAG2_TEXT_DIRECTION_MASK_SHIFT
};
/**
* Indicates whether the view text direction has been resolved.
* @hide
*/
- static final int TEXT_DIRECTION_RESOLVED = 0x00000008 << TEXT_DIRECTION_MASK_SHIFT;
+ static final int PFLAG2_TEXT_DIRECTION_RESOLVED = 0x00000008
+ << PFLAG2_TEXT_DIRECTION_MASK_SHIFT;
/**
* Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
* @hide
*/
- static final int TEXT_DIRECTION_RESOLVED_MASK_SHIFT = 10;
+ static final int PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT = 10;
/**
* Mask for use with private flags indicating bits used for resolved text direction.
* @hide
*/
- static final int TEXT_DIRECTION_RESOLVED_MASK = 0x00000007 << TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
+ static final int PFLAG2_TEXT_DIRECTION_RESOLVED_MASK = 0x00000007
+ << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
/**
* Indicates whether the view text direction has been resolved to the "first strong" heuristic.
* @hide
*/
- static final int TEXT_DIRECTION_RESOLVED_DEFAULT =
- TEXT_DIRECTION_FIRST_STRONG << TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
+ static final int PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT =
+ TEXT_DIRECTION_FIRST_STRONG << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
/*
* Default text alignment. The text alignment of this View is inherited from its parent.
* Use with {@link #setTextAlignment(int)}
- * @hide
*/
public static final int TEXT_ALIGNMENT_INHERIT = 0;
@@ -1994,7 +1981,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* ALIGN_CENTER, or ALIGN_OPPOSITE, which are relative to each paragraph’s text direction.
*
* Use with {@link #setTextAlignment(int)}
- * @hide
*/
public static final int TEXT_ALIGNMENT_GRAVITY = 1;
@@ -2002,7 +1988,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* Align to the start of the paragraph, e.g. ALIGN_NORMAL.
*
* Use with {@link #setTextAlignment(int)}
- * @hide
*/
public static final int TEXT_ALIGNMENT_TEXT_START = 2;
@@ -2010,7 +1995,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* Align to the end of the paragraph, e.g. ALIGN_OPPOSITE.
*
* Use with {@link #setTextAlignment(int)}
- * @hide
*/
public static final int TEXT_ALIGNMENT_TEXT_END = 3;
@@ -2018,7 +2002,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* Center the paragraph, e.g. ALIGN_CENTER.
*
* Use with {@link #setTextAlignment(int)}
- * @hide
*/
public static final int TEXT_ALIGNMENT_CENTER = 4;
@@ -2027,7 +2010,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* layoutDirection is LTR, and ALIGN_RIGHT otherwise.
*
* Use with {@link #setTextAlignment(int)}
- * @hide
*/
public static final int TEXT_ALIGNMENT_VIEW_START = 5;
@@ -2036,66 +2018,65 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* layoutDirection is LTR, and ALIGN_LEFT otherwise.
*
* Use with {@link #setTextAlignment(int)}
- * @hide
*/
public static final int TEXT_ALIGNMENT_VIEW_END = 6;
/**
* Default text alignment is inherited
- * @hide
*/
- protected static int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY;
+ public static int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY;
/**
* Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
* @hide
*/
- static final int TEXT_ALIGNMENT_MASK_SHIFT = 13;
+ static final int PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT = 13;
/**
* Mask for use with private flags indicating bits used for text alignment.
* @hide
*/
- static final int TEXT_ALIGNMENT_MASK = 0x00000007 << TEXT_ALIGNMENT_MASK_SHIFT;
+ static final int PFLAG2_TEXT_ALIGNMENT_MASK = 0x00000007 << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT;
/**
* Array of text direction flags for mapping attribute "textAlignment" to correct
* flag value.
* @hide
*/
- private static final int[] TEXT_ALIGNMENT_FLAGS = {
- TEXT_ALIGNMENT_INHERIT << TEXT_ALIGNMENT_MASK_SHIFT,
- TEXT_ALIGNMENT_GRAVITY << TEXT_ALIGNMENT_MASK_SHIFT,
- TEXT_ALIGNMENT_TEXT_START << TEXT_ALIGNMENT_MASK_SHIFT,
- TEXT_ALIGNMENT_TEXT_END << TEXT_ALIGNMENT_MASK_SHIFT,
- TEXT_ALIGNMENT_CENTER << TEXT_ALIGNMENT_MASK_SHIFT,
- TEXT_ALIGNMENT_VIEW_START << TEXT_ALIGNMENT_MASK_SHIFT,
- TEXT_ALIGNMENT_VIEW_END << TEXT_ALIGNMENT_MASK_SHIFT
+ private static final int[] PFLAG2_TEXT_ALIGNMENT_FLAGS = {
+ TEXT_ALIGNMENT_INHERIT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+ TEXT_ALIGNMENT_GRAVITY << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+ TEXT_ALIGNMENT_TEXT_START << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+ TEXT_ALIGNMENT_TEXT_END << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+ TEXT_ALIGNMENT_CENTER << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+ TEXT_ALIGNMENT_VIEW_START << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT,
+ TEXT_ALIGNMENT_VIEW_END << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT
};
/**
* Indicates whether the view text alignment has been resolved.
* @hide
*/
- static final int TEXT_ALIGNMENT_RESOLVED = 0x00000008 << TEXT_ALIGNMENT_MASK_SHIFT;
+ static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED = 0x00000008 << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT;
/**
* Bit shift to get the resolved text alignment.
* @hide
*/
- static final int TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT = 17;
+ static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT = 17;
/**
* Mask for use with private flags indicating bits used for text alignment.
* @hide
*/
- static final int TEXT_ALIGNMENT_RESOLVED_MASK = 0x00000007 << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+ static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK = 0x00000007
+ << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
/**
* Indicates whether if the view text alignment has been resolved to gravity
*/
- public static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT =
- TEXT_ALIGNMENT_GRAVITY << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+ private static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT =
+ TEXT_ALIGNMENT_GRAVITY << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
// Accessiblity constants for mPrivateFlags2
@@ -2103,7 +2084,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* Shift for the bits in {@link #mPrivateFlags2} related to the
* "importantForAccessibility" attribute.
*/
- static final int IMPORTANT_FOR_ACCESSIBILITY_SHIFT = 20;
+ static final int PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT = 20;
/**
* Automatically determine whether a view is important for accessibility.
@@ -2121,7 +2102,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 0x00000002;
/**
- * The default whether the view is important for accessiblity.
+ * The default whether the view is important for accessibility.
*/
static final int IMPORTANT_FOR_ACCESSIBILITY_DEFAULT = IMPORTANT_FOR_ACCESSIBILITY_AUTO;
@@ -2129,92 +2110,52 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* Mask for obtainig the bits which specify how to determine
* whether a view is important for accessibility.
*/
- static final int IMPORTANT_FOR_ACCESSIBILITY_MASK = (IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ static final int PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK = (IMPORTANT_FOR_ACCESSIBILITY_AUTO
| IMPORTANT_FOR_ACCESSIBILITY_YES | IMPORTANT_FOR_ACCESSIBILITY_NO)
- << IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+ << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
/**
* Flag indicating whether a view has accessibility focus.
*/
- static final int ACCESSIBILITY_FOCUSED = 0x00000040 << IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+ static final int PFLAG2_ACCESSIBILITY_FOCUSED = 0x00000040 << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
/**
* Flag indicating whether a view state for accessibility has changed.
*/
- static final int ACCESSIBILITY_STATE_CHANGED = 0x00000080 << IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+ static final int PFLAG2_ACCESSIBILITY_STATE_CHANGED = 0x00000080
+ << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
/**
* Flag indicating whether a view failed the quickReject() check in draw(). This condition
* is used to check whether later changes to the view's transform should invalidate the
* view to force the quickReject test to run again.
*/
- static final int VIEW_QUICK_REJECTED = 0x10000000;
-
- // Accessiblity constants for mPrivateFlags2
+ static final int PFLAG2_VIEW_QUICK_REJECTED = 0x10000000;
/**
- * Shift for the bits in {@link #mPrivateFlags2} related to the
- * "accessibilityFocusable" attribute.
+ * Flag indicating that start/end padding has been resolved into left/right padding
+ * for use in measurement, layout, drawing, etc. This is set by {@link #resolvePadding()}
+ * and checked by {@link #measure(int, int)} to determine if padding needs to be resolved
+ * during measurement. In some special cases this is required such as when an adapter-based
+ * view measures prospective children without attaching them to a window.
*/
- static final int ACCESSIBILITY_FOCUSABLE_SHIFT = 29;
+ static final int PFLAG2_PADDING_RESOLVED = 0x20000000;
/**
- * The system determines whether the view can take accessibility focus - default (recommended).
- * <p>
- * Such a view is consideted by the focus search if it is:
- * <ul>
- * <li>
- * Important for accessibility and actionable (clickable, long clickable, focusable)
- * </li>
- * <li>
- * Important for accessibility, not actionable (clickable, long clickable, focusable),
- * and does not have an actionable predecessor.
- * </li>
- * </ul>
- * An accessibility srvice can request putting accessibility focus on such a view.
- * </p>
- *
- * @hide
+ * Flag indicating that the start/end drawables has been resolved into left/right ones.
*/
- public static final int ACCESSIBILITY_FOCUSABLE_AUTO = 0x00000000;
+ static final int PFLAG2_DRAWABLE_RESOLVED = 0x40000000;
/**
- * The view can take accessibility focus.
- * <p>
- * A view that can take accessibility focus is always considered during focus
- * search and an accessibility service can request putting accessibility focus
- * on it.
- * </p>
- *
- * @hide
- */
- public static final int ACCESSIBILITY_FOCUSABLE_YES = 0x00000001;
-
- /**
- * The view can not take accessibility focus.
- * <p>
- * A view that can not take accessibility focus is never considered during focus
- * search and an accessibility service can not request putting accessibility focus
- * on it.
- * </p>
- *
- * @hide
+ * Group of bits indicating that RTL properties resolution is done.
*/
- public static final int ACCESSIBILITY_FOCUSABLE_NO = 0x00000002;
-
- /**
- * The default whether the view is accessiblity focusable.
- */
- static final int ACCESSIBILITY_FOCUSABLE_DEFAULT = ACCESSIBILITY_FOCUSABLE_AUTO;
-
- /**
- * Mask for obtainig the bits which specifies how to determine
- * whether a view is accessibility focusable.
- */
- static final int ACCESSIBILITY_FOCUSABLE_MASK = (ACCESSIBILITY_FOCUSABLE_AUTO
- | ACCESSIBILITY_FOCUSABLE_YES | ACCESSIBILITY_FOCUSABLE_NO)
- << ACCESSIBILITY_FOCUSABLE_SHIFT;
+ static final int ALL_RTL_PROPERTIES_RESOLVED = PFLAG2_LAYOUT_DIRECTION_RESOLVED |
+ PFLAG2_TEXT_DIRECTION_RESOLVED |
+ PFLAG2_TEXT_ALIGNMENT_RESOLVED |
+ PFLAG2_PADDING_RESOLVED |
+ PFLAG2_DRAWABLE_RESOLVED;
+ // There are a couple of flags left in mPrivateFlags2
/* End of masks for mPrivateFlags2 */
@@ -2225,19 +2166,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* an animation is cleared between successive frames, in order to tell the associated
* DisplayList to clear its animation matrix.
*/
- static final int VIEW_IS_ANIMATING_TRANSFORM = 0x1;
+ static final int PFLAG3_VIEW_IS_ANIMATING_TRANSFORM = 0x1;
/**
* Flag indicating that view has an alpha animation set on it. This is used to track whether an
* animation is cleared between successive frames, in order to tell the associated
* DisplayList to restore its alpha value.
*/
- static final int VIEW_IS_ANIMATING_ALPHA = 0x2;
+ static final int PFLAG3_VIEW_IS_ANIMATING_ALPHA = 0x2;
/* End of masks for mPrivateFlags3 */
- static final int DRAG_MASK = DRAG_CAN_ACCEPT | DRAG_HOVERED;
+ static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED;
/**
* Always allow a user to over-scroll this view, provided it is a
@@ -2604,16 +2545,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@hide}
*/
@ViewDebug.ExportedProperty(flagMapping = {
- @ViewDebug.FlagToString(mask = FORCE_LAYOUT, equals = FORCE_LAYOUT,
+ @ViewDebug.FlagToString(mask = PFLAG_FORCE_LAYOUT, equals = PFLAG_FORCE_LAYOUT,
name = "FORCE_LAYOUT"),
- @ViewDebug.FlagToString(mask = LAYOUT_REQUIRED, equals = LAYOUT_REQUIRED,
+ @ViewDebug.FlagToString(mask = PFLAG_LAYOUT_REQUIRED, equals = PFLAG_LAYOUT_REQUIRED,
name = "LAYOUT_REQUIRED"),
- @ViewDebug.FlagToString(mask = DRAWING_CACHE_VALID, equals = DRAWING_CACHE_VALID,
+ @ViewDebug.FlagToString(mask = PFLAG_DRAWING_CACHE_VALID, equals = PFLAG_DRAWING_CACHE_VALID,
name = "DRAWING_CACHE_INVALID", outputIf = false),
- @ViewDebug.FlagToString(mask = DRAWN, equals = DRAWN, name = "DRAWN", outputIf = true),
- @ViewDebug.FlagToString(mask = DRAWN, equals = DRAWN, name = "NOT_DRAWN", outputIf = false),
- @ViewDebug.FlagToString(mask = DIRTY_MASK, equals = DIRTY_OPAQUE, name = "DIRTY_OPAQUE"),
- @ViewDebug.FlagToString(mask = DIRTY_MASK, equals = DIRTY, name = "DIRTY")
+ @ViewDebug.FlagToString(mask = PFLAG_DRAWN, equals = PFLAG_DRAWN, name = "DRAWN", outputIf = true),
+ @ViewDebug.FlagToString(mask = PFLAG_DRAWN, equals = PFLAG_DRAWN, name = "NOT_DRAWN", outputIf = false),
+ @ViewDebug.FlagToString(mask = PFLAG_DIRTY_MASK, equals = PFLAG_DIRTY_OPAQUE, name = "DIRTY_OPAQUE"),
+ @ViewDebug.FlagToString(mask = PFLAG_DIRTY_MASK, equals = PFLAG_DIRTY, name = "DIRTY")
})
int mPrivateFlags;
int mPrivateFlags2;
@@ -2846,14 +2787,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "padding")
- protected int mPaddingLeft;
+ protected int mPaddingLeft = 0;
/**
* The right padding in pixels, that is the distance in pixels between the
* right edge of this view and the right edge of its content.
* {@hide}
*/
@ViewDebug.ExportedProperty(category = "padding")
- protected int mPaddingRight;
+ protected int mPaddingRight = 0;
/**
* The top padding in pixels, that is the distance in pixels between the
* top edge of this view and the top edge of its content.
@@ -2881,6 +2822,23 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
private CharSequence mContentDescription;
/**
+ * Specifies the id of a view for which this view serves as a label for
+ * accessibility purposes.
+ */
+ private int mLabelForId = View.NO_ID;
+
+ /**
+ * Predicate for matching labeled view id with its label for
+ * accessibility purposes.
+ */
+ private MatchLabelForPredicate mMatchLabelForPredicate;
+
+ /**
+ * Predicate for matching a view by its id.
+ */
+ private MatchIdPredicate mMatchIdPredicate;
+
+ /**
* Cache the paddingRight set by the user to append to the scrollbar's size.
*
* @hide
@@ -2905,13 +2863,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
protected int mUserPaddingLeft;
/**
- * Cache if the user padding is relative.
- *
- */
- @ViewDebug.ExportedProperty(category = "padding")
- boolean mUserPaddingRelative;
-
- /**
* Cache the paddingStart set by the user to append to the scrollbar's size.
*
*/
@@ -2926,6 +2877,25 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int mUserPaddingEnd;
/**
+ * Cache initial left padding.
+ *
+ * @hide
+ */
+ int mUserPaddingLeftInitial = 0;
+
+ /**
+ * Cache initial right padding.
+ *
+ * @hide
+ */
+ int mUserPaddingRightInitial = 0;
+
+ /**
+ * Default undefined padding
+ */
+ private static final int UNDEFINED_PADDING = Integer.MIN_VALUE;
+
+ /**
* @hide
*/
int mOldWidthMeasureSpec = Integer.MIN_VALUE;
@@ -3218,6 +3188,21 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
private boolean mSendingHoverAccessibilityEvents;
/**
+ * Delegate for injecting accessibility functionality.
+ */
+ AccessibilityDelegate mAccessibilityDelegate;
+
+ /**
+ * Consistency verifier for debugging purposes.
+ * @hide
+ */
+ protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
+ InputEventConsistencyVerifier.isInstrumentationEnabled() ?
+ new InputEventConsistencyVerifier(this, 0) : null;
+
+ private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
+
+ /**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
@@ -3228,32 +3213,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mResources = context != null ? context.getResources() : null;
mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
// Set layout and text direction defaults
- mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << LAYOUT_DIRECTION_MASK_SHIFT) |
- (TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT) |
- (TEXT_ALIGNMENT_DEFAULT << TEXT_ALIGNMENT_MASK_SHIFT) |
- (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << IMPORTANT_FOR_ACCESSIBILITY_SHIFT) |
- (ACCESSIBILITY_FOCUSABLE_DEFAULT << ACCESSIBILITY_FOCUSABLE_SHIFT);
+ mPrivateFlags2 =
+ (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
+ (TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) |
+ (PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT) |
+ (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
+ (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
+ (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
- mUserPaddingStart = -1;
- mUserPaddingEnd = -1;
- mUserPaddingRelative = false;
+ mUserPaddingStart = UNDEFINED_PADDING;
+ mUserPaddingEnd = UNDEFINED_PADDING;
}
/**
- * Delegate for injecting accessiblity functionality.
- */
- AccessibilityDelegate mAccessibilityDelegate;
-
- /**
- * Consistency verifier for debugging purposes.
- * @hide
- */
- protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
- InputEventConsistencyVerifier.isInstrumentationEnabled() ?
- new InputEventConsistencyVerifier(this, 0) : null;
-
- /**
* Constructor that is called when inflating a view from XML. This is called
* when a view is being constructed from an XML file, supplying attributes
* that were specified in the XML file. This version uses a default style of
@@ -3303,8 +3276,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int topPadding = -1;
int rightPadding = -1;
int bottomPadding = -1;
- int startPadding = -1;
- int endPadding = -1;
+ int startPadding = UNDEFINED_PADDING;
+ int endPadding = UNDEFINED_PADDING;
int padding = -1;
@@ -3326,8 +3299,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
boolean transformSet = false;
int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
-
int overScrollMode = mOverScrollMode;
+ boolean initializeScrollbars = false;
+
+ boolean leftPaddingDefined = false;
+ boolean rightPaddingDefined = false;
+ boolean startPaddingDefined = false;
+ boolean endPaddingDefined = false;
+
+ final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
@@ -3337,24 +3318,34 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
break;
case com.android.internal.R.styleable.View_padding:
padding = a.getDimensionPixelSize(attr, -1);
+ mUserPaddingLeftInitial = padding;
+ mUserPaddingRightInitial = padding;
+ leftPaddingDefined = true;
+ rightPaddingDefined = true;
break;
case com.android.internal.R.styleable.View_paddingLeft:
leftPadding = a.getDimensionPixelSize(attr, -1);
+ mUserPaddingLeftInitial = leftPadding;
+ leftPaddingDefined = true;
break;
case com.android.internal.R.styleable.View_paddingTop:
topPadding = a.getDimensionPixelSize(attr, -1);
break;
case com.android.internal.R.styleable.View_paddingRight:
rightPadding = a.getDimensionPixelSize(attr, -1);
+ mUserPaddingRightInitial = rightPadding;
+ rightPaddingDefined = true;
break;
case com.android.internal.R.styleable.View_paddingBottom:
bottomPadding = a.getDimensionPixelSize(attr, -1);
break;
case com.android.internal.R.styleable.View_paddingStart:
- startPadding = a.getDimensionPixelSize(attr, -1);
+ startPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING);
+ startPaddingDefined = true;
break;
case com.android.internal.R.styleable.View_paddingEnd:
- endPadding = a.getDimensionPixelSize(attr, -1);
+ endPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING);
+ endPaddingDefined = true;
break;
case com.android.internal.R.styleable.View_scrollX:
x = a.getDimensionPixelOffset(attr, 0);
@@ -3456,12 +3447,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
break;
case com.android.internal.R.styleable.View_layoutDirection:
// Clear any layout direction flags (included resolved bits) already set
- mPrivateFlags2 &= ~(LAYOUT_DIRECTION_MASK | LAYOUT_DIRECTION_RESOLVED_MASK);
+ mPrivateFlags2 &=
+ ~(PFLAG2_LAYOUT_DIRECTION_MASK | PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK);
// Set the layout direction flags depending on the value of the attribute
final int layoutDirection = a.getInt(attr, -1);
final int value = (layoutDirection != -1) ?
LAYOUT_DIRECTION_FLAGS[layoutDirection] : LAYOUT_DIRECTION_DEFAULT;
- mPrivateFlags2 |= (value << LAYOUT_DIRECTION_MASK_SHIFT);
+ mPrivateFlags2 |= (value << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT);
break;
case com.android.internal.R.styleable.View_drawingCacheQuality:
final int cacheQuality = a.getInt(attr, 0);
@@ -3473,6 +3465,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
case com.android.internal.R.styleable.View_contentDescription:
setContentDescription(a.getString(attr));
break;
+ case com.android.internal.R.styleable.View_labelFor:
+ setLabelFor(a.getResourceId(attr, NO_ID));
+ break;
case com.android.internal.R.styleable.View_soundEffectsEnabled:
if (!a.getBoolean(attr, true)) {
viewFlagValues &= ~SOUND_EFFECTS_ENABLED;
@@ -3490,12 +3485,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (scrollbars != SCROLLBARS_NONE) {
viewFlagValues |= scrollbars;
viewFlagMasks |= SCROLLBARS_MASK;
- initializeScrollbars(a);
+ initializeScrollbars = true;
}
break;
//noinspection deprecation
case R.styleable.View_fadingEdge:
- if (context.getApplicationInfo().targetSdkVersion >= ICE_CREAM_SANDWICH) {
+ if (targetSdkVersion >= ICE_CREAM_SANDWICH) {
// Ignore the attribute starting with ICS
break;
}
@@ -3606,19 +3601,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
break;
case R.styleable.View_textDirection:
// Clear any text direction flag already set
- mPrivateFlags2 &= ~TEXT_DIRECTION_MASK;
+ mPrivateFlags2 &= ~PFLAG2_TEXT_DIRECTION_MASK;
// Set the text direction flags depending on the value of the attribute
final int textDirection = a.getInt(attr, -1);
if (textDirection != -1) {
- mPrivateFlags2 |= TEXT_DIRECTION_FLAGS[textDirection];
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_FLAGS[textDirection];
}
break;
case R.styleable.View_textAlignment:
// Clear any text alignment flag already set
- mPrivateFlags2 &= ~TEXT_ALIGNMENT_MASK;
+ mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK;
// Set the text alignment flag depending on the value of the attribute
final int textAlignment = a.getInt(attr, TEXT_ALIGNMENT_DEFAULT);
- mPrivateFlags2 |= TEXT_ALIGNMENT_FLAGS[textAlignment];
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_FLAGS[textAlignment];
break;
case R.styleable.View_importantForAccessibility:
setImportantForAccessibility(a.getInt(attr,
@@ -3627,41 +3622,78 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
}
- a.recycle();
-
setOverScrollMode(overScrollMode);
- if (background != null) {
- setBackground(background);
- }
-
- // Cache user padding as we cannot fully resolve padding here (we dont have yet the resolved
- // layout direction). Those cached values will be used later during padding resolution.
+ // Cache start/end user padding as we cannot fully resolve padding here (we dont have yet
+ // the resolved layout direction). Those cached values will be used later during padding
+ // resolution.
mUserPaddingStart = startPadding;
mUserPaddingEnd = endPadding;
- updateUserPaddingRelative();
+ if (background != null) {
+ setBackground(background);
+ }
if (padding >= 0) {
leftPadding = padding;
topPadding = padding;
rightPadding = padding;
bottomPadding = padding;
+ mUserPaddingLeftInitial = padding;
+ mUserPaddingRightInitial = padding;
+ }
+
+ if (isRtlCompatibilityMode()) {
+ // RTL compatibility mode: pre Jelly Bean MR1 case OR no RTL support case.
+ // left / right padding are used if defined (meaning here nothing to do). If they are not
+ // defined and start / end padding are defined (e.g. in Frameworks resources), then we use
+ // start / end and resolve them as left / right (layout direction is not taken into account).
+ // Padding from the background drawable is stored at this point in mUserPaddingLeftInitial
+ // and mUserPaddingRightInitial) so drawable padding will be used as ultimate default if
+ // defined.
+ if (!leftPaddingDefined && startPaddingDefined) {
+ leftPadding = startPadding;
+ }
+ mUserPaddingLeftInitial = (leftPadding >= 0) ? leftPadding : mUserPaddingLeftInitial;
+ if (!rightPaddingDefined && endPaddingDefined) {
+ rightPadding = endPadding;
+ }
+ mUserPaddingRightInitial = (rightPadding >= 0) ? rightPadding : mUserPaddingRightInitial;
+ } else {
+ // Jelly Bean MR1 and after case: if start/end defined, they will override any left/right
+ // values defined. Otherwise, left /right values are used.
+ // Padding from the background drawable is stored at this point in mUserPaddingLeftInitial
+ // and mUserPaddingRightInitial) so drawable padding will be used as ultimate default if
+ // defined.
+ if (startPaddingDefined) {
+ mUserPaddingLeftInitial = startPadding;
+ } else if (leftPaddingDefined) {
+ mUserPaddingLeftInitial = leftPadding;
+ }
+ if (endPaddingDefined) {
+ mUserPaddingRightInitial = endPadding;
+ }
+ else if (rightPaddingDefined) {
+ mUserPaddingRightInitial = rightPadding;
+ }
}
- // If the user specified the padding (either with android:padding or
- // android:paddingLeft/Top/Right/Bottom), use this padding, otherwise
- // use the default padding or the padding from the background drawable
- // (stored at this point in mPadding*)
- setPadding(leftPadding >= 0 ? leftPadding : mPaddingLeft,
+ internalSetPadding(
+ mUserPaddingLeftInitial,
topPadding >= 0 ? topPadding : mPaddingTop,
- rightPadding >= 0 ? rightPadding : mPaddingRight,
+ mUserPaddingRightInitial,
bottomPadding >= 0 ? bottomPadding : mPaddingBottom);
if (viewFlagMasks != 0) {
setFlags(viewFlagValues, viewFlagMasks);
}
+ if (initializeScrollbars) {
+ initializeScrollbars(a);
+ }
+
+ a.recycle();
+
// Needs to be called after mViewFlags is set
if (scrollbarStyle != SCROLLBARS_INSIDE_OVERLAY) {
recomputePadding();
@@ -3688,10 +3720,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
computeOpaqueFlags();
}
- private void updateUserPaddingRelative() {
- mUserPaddingRelative = (mUserPaddingStart >= 0 || mUserPaddingEnd >= 0);
- }
-
/**
* Non-public constructor for use in testing
*/
@@ -3699,6 +3727,81 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mResources = null;
}
+ public String toString() {
+ StringBuilder out = new StringBuilder(128);
+ out.append(getClass().getName());
+ out.append('{');
+ out.append(Integer.toHexString(System.identityHashCode(this)));
+ out.append(' ');
+ switch (mViewFlags&VISIBILITY_MASK) {
+ case VISIBLE: out.append('V'); break;
+ case INVISIBLE: out.append('I'); break;
+ case GONE: out.append('G'); break;
+ default: out.append('.'); break;
+ }
+ out.append((mViewFlags&FOCUSABLE_MASK) == FOCUSABLE ? 'F' : '.');
+ out.append((mViewFlags&ENABLED_MASK) == ENABLED ? 'E' : '.');
+ out.append((mViewFlags&DRAW_MASK) == WILL_NOT_DRAW ? '.' : 'D');
+ out.append((mViewFlags&SCROLLBARS_HORIZONTAL) != 0 ? 'H' : '.');
+ out.append((mViewFlags&SCROLLBARS_VERTICAL) != 0 ? 'V' : '.');
+ out.append((mViewFlags&CLICKABLE) != 0 ? 'C' : '.');
+ out.append((mViewFlags&LONG_CLICKABLE) != 0 ? 'L' : '.');
+ out.append(' ');
+ out.append((mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE) != 0 ? 'R' : '.');
+ out.append((mPrivateFlags&PFLAG_FOCUSED) != 0 ? 'F' : '.');
+ out.append((mPrivateFlags&PFLAG_SELECTED) != 0 ? 'S' : '.');
+ if ((mPrivateFlags&PFLAG_PREPRESSED) != 0) {
+ out.append('p');
+ } else {
+ out.append((mPrivateFlags&PFLAG_PRESSED) != 0 ? 'P' : '.');
+ }
+ out.append((mPrivateFlags&PFLAG_HOVERED) != 0 ? 'H' : '.');
+ out.append((mPrivateFlags&PFLAG_ACTIVATED) != 0 ? 'A' : '.');
+ out.append((mPrivateFlags&PFLAG_INVALIDATED) != 0 ? 'I' : '.');
+ out.append((mPrivateFlags&PFLAG_DIRTY_MASK) != 0 ? 'D' : '.');
+ out.append(' ');
+ out.append(mLeft);
+ out.append(',');
+ out.append(mTop);
+ out.append('-');
+ out.append(mRight);
+ out.append(',');
+ out.append(mBottom);
+ final int id = getId();
+ if (id != NO_ID) {
+ out.append(" #");
+ out.append(Integer.toHexString(id));
+ final Resources r = mResources;
+ if (id != 0 && r != null) {
+ try {
+ String pkgname;
+ switch (id&0xff000000) {
+ case 0x7f000000:
+ pkgname="app";
+ break;
+ case 0x01000000:
+ pkgname="android";
+ break;
+ default:
+ pkgname = r.getResourcePackageName(id);
+ break;
+ }
+ String typename = r.getResourceTypeName(id);
+ String entryname = r.getResourceEntryName(id);
+ out.append(" ");
+ out.append(pkgname);
+ out.append(":");
+ out.append(typename);
+ out.append("/");
+ out.append(entryname);
+ } catch (Resources.NotFoundException e) {
+ }
+ }
+ }
+ out.append("}");
+ return out.toString();
+ }
+
/**
* <p>
* Initializes the fading edges from a given set of styled attributes. This
@@ -3881,6 +3984,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
scrollabilityCache.scrollBar.setAlwaysDrawVerticalTrack(true);
}
+ // Apply layout direction to the new Drawables if needed
+ final int layoutDirection = getLayoutDirection();
+ if (track != null) {
+ track.setLayoutDirection(layoutDirection);
+ }
+ if (thumb != null) {
+ thumb.setLayoutDirection(layoutDirection);
+ }
+
// Re-apply user/background padding so that scrollbar(s) get added
resolvePadding();
}
@@ -4247,8 +4359,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
System.out.println(this + " requestFocus()");
}
- if ((mPrivateFlags & FOCUSED) == 0) {
- mPrivateFlags |= FOCUSED;
+ if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
+ mPrivateFlags |= PFLAG_FOCUSED;
if (mParent != null) {
mParent.requestChildFocus(this, this);
@@ -4294,25 +4406,42 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @return Whether any parent scrolled.
*/
public boolean requestRectangleOnScreen(Rect rectangle, boolean immediate) {
+ if (mParent == null) {
+ return false;
+ }
+
View child = this;
+
+ RectF position = (mAttachInfo != null) ? mAttachInfo.mTmpTransformRect : new RectF();
+ position.set(rectangle);
+
ViewParent parent = mParent;
boolean scrolled = false;
while (parent != null) {
+ rectangle.set((int) position.left, (int) position.top,
+ (int) position.right, (int) position.bottom);
+
scrolled |= parent.requestChildRectangleOnScreen(child,
rectangle, immediate);
- // offset rect so next call has the rectangle in the
- // coordinate system of its direct child.
- rectangle.offset(child.getLeft(), child.getTop());
- rectangle.offset(-child.getScrollX(), -child.getScrollY());
+ if (!child.hasIdentityMatrix()) {
+ child.getMatrix().mapRect(position);
+ }
+
+ position.offset(child.mLeft, child.mTop);
if (!(parent instanceof View)) {
break;
}
- child = (View) parent;
+ View parentView = (View) parent;
+
+ position.offset(-parentView.getScrollX(), -parentView.getScrollY());
+
+ child = parentView;
parent = child.getParent();
}
+
return scrolled;
}
@@ -4332,8 +4461,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
System.out.println(this + " clearFocus()");
}
- if ((mPrivateFlags & FOCUSED) != 0) {
- mPrivateFlags &= ~FOCUSED;
+ if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
+ mPrivateFlags &= ~PFLAG_FOCUSED;
if (mParent != null) {
mParent.clearChildFocus(this);
@@ -4367,8 +4496,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
System.out.println(this + " unFocus()");
}
- if ((mPrivateFlags & FOCUSED) != 0) {
- mPrivateFlags &= ~FOCUSED;
+ if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
+ mPrivateFlags &= ~PFLAG_FOCUSED;
onFocusChanged(false, 0, null);
refreshDrawableState();
@@ -4387,7 +4516,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
@ViewDebug.ExportedProperty(category = "focus")
public boolean hasFocus() {
- return (mPrivateFlags & FOCUSED) != 0;
+ return (mPrivateFlags & PFLAG_FOCUSED) != 0;
}
/**
@@ -4458,7 +4587,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
- * Sends an accessibility event of the given type. If accessiiblity is
+ * Sends an accessibility event of the given type. If accessibility is
* not enabled this method has no effect. The default implementation calls
* {@link #onInitializeAccessibilityEvent(AccessibilityEvent)} first
* to populate information about the event source (this View), then calls
@@ -4502,11 +4631,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param text The announcement text.
*/
public void announceForAccessibility(CharSequence text) {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_ANNOUNCEMENT);
+ onInitializeAccessibilityEvent(event);
event.getText().add(text);
- sendAccessibilityEventUnchecked(event);
+ event.setContentDescription(null);
+ mParent.requestSendAccessibilityEvent(this, event);
}
}
@@ -4826,6 +4957,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
Rect bounds = mAttachInfo.mTmpInvalRect;
+
getDrawingRect(bounds);
info.setBoundsInParent(bounds);
@@ -4837,6 +4969,28 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
info.setParent((View) parent);
}
+ if (mID != View.NO_ID) {
+ View rootView = getRootView();
+ if (rootView == null) {
+ rootView = this;
+ }
+ View label = rootView.findLabelForView(this, mID);
+ if (label != null) {
+ info.setLabeledBy(label);
+ }
+ }
+
+ if (mLabelForId != View.NO_ID) {
+ View rootView = getRootView();
+ if (rootView == null) {
+ rootView = this;
+ }
+ View labeled = rootView.findViewInsideOutShouldExist(this, mLabelForId);
+ if (labeled != null) {
+ info.setLabelFor(labeled);
+ }
+ }
+
info.setVisibleToUser(isVisibleToUser());
info.setPackageName(mContext.getPackageName());
@@ -4852,7 +5006,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
info.setLongClickable(isLongClickable());
// TODO: These make sense only if we are in an AdapterView but all
- // views can be selected. Maybe from accessiiblity perspective
+ // views can be selected. Maybe from accessibility perspective
// we should report as selectable view in an AdapterView.
info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
@@ -4866,10 +5020,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
if (!isAccessibilityFocused()) {
- final int mode = getAccessibilityFocusable();
- if (mode == ACCESSIBILITY_FOCUSABLE_YES || mode == ACCESSIBILITY_FOCUSABLE_AUTO) {
- info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
- }
+ info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
} else {
info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
}
@@ -4891,28 +5042,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
}
- /**
- * Returns the delta between the actual and last reported window left.
- *
- * @hide
- */
- public int getActualAndReportedWindowLeftDelta() {
- if (mAttachInfo != null) {
- return mAttachInfo.mActualWindowLeft - mAttachInfo.mWindowLeft;
- }
- return 0;
- }
-
- /**
- * Returns the delta between the actual and last reported window top.
- *
- * @hide
- */
- public int getActualAndReportedWindowTopDelta() {
- if (mAttachInfo != null) {
- return mAttachInfo.mActualWindowTop - mAttachInfo.mWindowTop;
+ private View findLabelForView(View view, int labeledId) {
+ if (mMatchLabelForPredicate == null) {
+ mMatchLabelForPredicate = new MatchLabelForPredicate();
}
- return 0;
+ mMatchLabelForPredicate.mLabeledId = labeledId;
+ return findViewByPredicateInsideOut(view, mMatchLabelForPredicate);
}
/**
@@ -4929,9 +5064,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
- * Computes whether the given portion of this view is visible to the user. Such a view is
- * attached, visible, all its predecessors are visible, has an alpha greater than zero, and
- * the specified portion is not clipped entirely by its predecessors.
+ * Computes whether the given portion of this view is visible to the user.
+ * Such a view is attached, visible, all its predecessors are visible,
+ * has an alpha greater than zero, and the specified portion is not
+ * clipped entirely by its predecessors.
*
* @param boundInView the portion of the view to test; coordinates should be relative; may be
* <code>null</code>, and the entire view will be tested in this case.
@@ -4945,26 +5081,42 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @hide
*/
protected boolean isVisibleToUser(Rect boundInView) {
- Rect visibleRect = mAttachInfo.mTmpInvalRect;
- Point offset = mAttachInfo.mPoint;
- // The first two checks are made also made by isShown() which
- // however traverses the tree up to the parent to catch that.
- // Therefore, we do some fail fast check to minimize the up
- // tree traversal.
- boolean isVisible = mAttachInfo != null
- && mAttachInfo.mWindowVisibility == View.VISIBLE
- && getAlpha() > 0
- && isShown()
- && getGlobalVisibleRect(visibleRect, offset);
+ if (mAttachInfo != null) {
+ Rect visibleRect = mAttachInfo.mTmpInvalRect;
+ Point offset = mAttachInfo.mPoint;
+ // The first two checks are made also made by isShown() which
+ // however traverses the tree up to the parent to catch that.
+ // Therefore, we do some fail fast check to minimize the up
+ // tree traversal.
+ boolean isVisible = mAttachInfo.mWindowVisibility == View.VISIBLE
+ && getAlpha() > 0
+ && isShown()
+ && getGlobalVisibleRect(visibleRect, offset);
if (isVisible && boundInView != null) {
visibleRect.offset(-offset.x, -offset.y);
- isVisible &= boundInView.intersect(visibleRect);
+ // isVisible is always true here, use a simple assignment
+ isVisible = boundInView.intersect(visibleRect);
}
return isVisible;
+ }
+
+ return false;
}
/**
- * Sets a delegate for implementing accessibility support via compositon as
+ * Returns the delegate for implementing accessibility support via
+ * composition. For more details see {@link AccessibilityDelegate}.
+ *
+ * @return The delegate, or null if none set.
+ *
+ * @hide
+ */
+ public AccessibilityDelegate getAccessibilityDelegate() {
+ return mAccessibilityDelegate;
+ }
+
+ /**
+ * Sets a delegate for implementing accessibility support via composition as
* opposed to inheritance. The delegate's primary use is for implementing
* backwards compatible widgets. For more details see {@link AccessibilityDelegate}.
*
@@ -5069,6 +5221,32 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Gets the id of a view for which this view serves as a label for
+ * accessibility purposes.
+ *
+ * @return The labeled view id.
+ */
+ @ViewDebug.ExportedProperty(category = "accessibility")
+ public int getLabelFor() {
+ return mLabelForId;
+ }
+
+ /**
+ * Sets the id of a view for which this view serves as a label for
+ * accessibility purposes.
+ *
+ * @param id The labeled view id.
+ */
+ @RemotableViewMethod
+ public void setLabelFor(int id) {
+ mLabelForId = id;
+ if (mLabelForId != View.NO_ID
+ && mID == View.NO_ID) {
+ mID = generateViewId();
+ }
+ }
+
+ /**
* Invoked whenever this view loses focus, either by losing window focus or by losing
* focus within its window. This method can be used to clear any state tied to the
* focus. For instance, if a button is held pressed with the trackball and the window
@@ -5106,7 +5284,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
@ViewDebug.ExportedProperty(category = "focus")
public boolean isFocused() {
- return (mPrivateFlags & FOCUSED) != 0;
+ return (mPrivateFlags & PFLAG_FOCUSED) != 0;
}
/**
@@ -5117,7 +5295,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* be found.
*/
public View findFocus() {
- return (mPrivateFlags & FOCUSED) != 0 ? this : null;
+ return (mPrivateFlags & PFLAG_FOCUSED) != 0 ? this : null;
}
/**
@@ -5130,7 +5308,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @attr ref android.R.styleable#View_isScrollContainer
*/
public boolean isScrollContainer() {
- return (mPrivateFlags & SCROLL_CONTAINER_ADDED) != 0;
+ return (mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0;
}
/**
@@ -5144,16 +5322,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public void setScrollContainer(boolean isScrollContainer) {
if (isScrollContainer) {
- if (mAttachInfo != null && (mPrivateFlags&SCROLL_CONTAINER_ADDED) == 0) {
+ if (mAttachInfo != null && (mPrivateFlags&PFLAG_SCROLL_CONTAINER_ADDED) == 0) {
mAttachInfo.mScrollContainers.add(this);
- mPrivateFlags |= SCROLL_CONTAINER_ADDED;
+ mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
- mPrivateFlags |= SCROLL_CONTAINER;
+ mPrivateFlags |= PFLAG_SCROLL_CONTAINER;
} else {
- if ((mPrivateFlags&SCROLL_CONTAINER_ADDED) != 0) {
+ if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
mAttachInfo.mScrollContainers.remove(this);
}
- mPrivateFlags &= ~(SCROLL_CONTAINER|SCROLL_CONTAINER_ADDED);
+ mPrivateFlags &= ~(PFLAG_SCROLL_CONTAINER|PFLAG_SCROLL_CONTAINER_ADDED);
}
}
@@ -5403,14 +5581,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @return Return true if this view applied the insets and it should not
* continue propagating further down the hierarchy, false otherwise.
* @see #getFitsSystemWindows()
- * @see #setFitsSystemWindows()
+ * @see #setFitsSystemWindows(boolean)
* @see #setSystemUiVisibility(int)
*/
protected boolean fitSystemWindows(Rect insets) {
if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
- mUserPaddingStart = -1;
- mUserPaddingEnd = -1;
- mUserPaddingRelative = false;
+ mUserPaddingStart = UNDEFINED_PADDING;
+ mUserPaddingEnd = UNDEFINED_PADDING;
if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
|| mAttachInfo == null
|| (mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0) {
@@ -5652,8 +5829,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@link #LAYOUT_DIRECTION_RTL},
* {@link #LAYOUT_DIRECTION_INHERIT} or
* {@link #LAYOUT_DIRECTION_LOCALE}.
- *
* @attr ref android.R.styleable#View_layoutDirection
+ *
* @hide
*/
@ViewDebug.ExportedProperty(category = "layout", mapping = {
@@ -5662,32 +5839,38 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
@ViewDebug.IntToString(from = LAYOUT_DIRECTION_INHERIT, to = "INHERIT"),
@ViewDebug.IntToString(from = LAYOUT_DIRECTION_LOCALE, to = "LOCALE")
})
- public int getLayoutDirection() {
- return (mPrivateFlags2 & LAYOUT_DIRECTION_MASK) >> LAYOUT_DIRECTION_MASK_SHIFT;
+ public int getRawLayoutDirection() {
+ return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >> PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
}
/**
* Set the layout direction for this view. This will propagate a reset of layout direction
* resolution to the view's children and resolve layout direction for this view.
*
- * @param layoutDirection One of {@link #LAYOUT_DIRECTION_LTR},
- * {@link #LAYOUT_DIRECTION_RTL},
- * {@link #LAYOUT_DIRECTION_INHERIT} or
- * {@link #LAYOUT_DIRECTION_LOCALE}.
+ * @param layoutDirection the layout direction to set. Should be one of:
+ *
+ * {@link #LAYOUT_DIRECTION_LTR},
+ * {@link #LAYOUT_DIRECTION_RTL},
+ * {@link #LAYOUT_DIRECTION_INHERIT},
+ * {@link #LAYOUT_DIRECTION_LOCALE}.
+ *
+ * Resolution will be done if the value is set to LAYOUT_DIRECTION_INHERIT. The resolution
+ * proceeds up the parent chain of the view to get the value. If there is no parent, then it
+ * will return the default {@link #LAYOUT_DIRECTION_LTR}.
*
* @attr ref android.R.styleable#View_layoutDirection
- * @hide
*/
@RemotableViewMethod
public void setLayoutDirection(int layoutDirection) {
- if (getLayoutDirection() != layoutDirection) {
+ if (getRawLayoutDirection() != layoutDirection) {
// Reset the current layout direction and the resolved one
- mPrivateFlags2 &= ~LAYOUT_DIRECTION_MASK;
- resetResolvedLayoutDirection();
- // Set the new layout direction (filtered) and ask for a layout pass
+ mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_MASK;
+ resetRtlProperties();
+ // Set the new layout direction (filtered)
mPrivateFlags2 |=
- ((layoutDirection << LAYOUT_DIRECTION_MASK_SHIFT) & LAYOUT_DIRECTION_MASK);
- requestLayout();
+ ((layoutDirection << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) & PFLAG2_LAYOUT_DIRECTION_MASK);
+ // We need to resolve all RTL properties as they all depend on layout direction
+ resolveRtlPropertiesIfNeeded();
}
}
@@ -5696,19 +5879,22 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @return {@link #LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns
* {@link #LAYOUT_DIRECTION_LTR} if the layout direction is not RTL.
- * @hide
+ *
+ * For compatibility, this will return {@link #LAYOUT_DIRECTION_LTR} if API version
+ * is lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}.
*/
@ViewDebug.ExportedProperty(category = "layout", mapping = {
@ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "RESOLVED_DIRECTION_LTR"),
@ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL, to = "RESOLVED_DIRECTION_RTL")
})
- public int getResolvedLayoutDirection() {
- // The layout diretion will be resolved only if needed
- if ((mPrivateFlags2 & LAYOUT_DIRECTION_RESOLVED) != LAYOUT_DIRECTION_RESOLVED) {
- resolveLayoutDirection();
+ public int getLayoutDirection() {
+ final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+ if (targetSdkVersion < JELLY_BEAN_MR1) {
+ mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+ return LAYOUT_DIRECTION_LTR;
}
- return ((mPrivateFlags2 & LAYOUT_DIRECTION_RESOLVED_RTL) == LAYOUT_DIRECTION_RESOLVED_RTL) ?
- LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
+ return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ==
+ PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
}
/**
@@ -5716,11 +5902,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* layout attribute and/or the inherited value from the parent
*
* @return true if the layout is right-to-left.
+ *
* @hide
*/
@ViewDebug.ExportedProperty(category = "layout")
public boolean isLayoutRtl() {
- return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
}
/**
@@ -5737,7 +5924,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
@ViewDebug.ExportedProperty(category = "layout")
public boolean hasTransientState() {
- return (mPrivateFlags2 & HAS_TRANSIENT_STATE) == HAS_TRANSIENT_STATE;
+ return (mPrivateFlags2 & PFLAG2_HAS_TRANSIENT_STATE) == PFLAG2_HAS_TRANSIENT_STATE;
}
/**
@@ -5764,8 +5951,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if ((hasTransientState && mTransientStateCount == 1) ||
(!hasTransientState && mTransientStateCount == 0)) {
// update flag if we've just incremented up from 0 or decremented down to 0
- mPrivateFlags2 = (mPrivateFlags2 & ~HAS_TRANSIENT_STATE) |
- (hasTransientState ? HAS_TRANSIENT_STATE : 0);
+ mPrivateFlags2 = (mPrivateFlags2 & ~PFLAG2_HAS_TRANSIENT_STATE) |
+ (hasTransientState ? PFLAG2_HAS_TRANSIENT_STATE : 0);
if (mParent != null) {
try {
mParent.childHasTransientStateChanged(this, hasTransientState);
@@ -5888,12 +6075,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* the View's internal state from a previously set "pressed" state.
*/
public void setPressed(boolean pressed) {
- final boolean needsRefresh = pressed != ((mPrivateFlags & PRESSED) == PRESSED);
+ final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);
if (pressed) {
- mPrivateFlags |= PRESSED;
+ mPrivateFlags |= PFLAG_PRESSED;
} else {
- mPrivateFlags &= ~PRESSED;
+ mPrivateFlags &= ~PFLAG_PRESSED;
}
if (needsRefresh) {
@@ -5924,7 +6111,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @return true if the view is currently pressed, false otherwise
*/
public boolean isPressed() {
- return (mPrivateFlags & PRESSED) == PRESSED;
+ return (mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED;
}
/**
@@ -6117,17 +6304,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
return null;
}
- private View findViewInsideOutShouldExist(View root, final int childViewId) {
- View result = root.findViewByPredicateInsideOut(this, new Predicate<View>() {
- @Override
- public boolean apply(View t) {
- return t.mID == childViewId;
- }
- });
-
+ private View findViewInsideOutShouldExist(View root, int id) {
+ if (mMatchIdPredicate == null) {
+ mMatchIdPredicate = new MatchIdPredicate();
+ }
+ mMatchIdPredicate.mId = id;
+ View result = root.findViewByPredicateInsideOut(this, mMatchIdPredicate);
if (result == null) {
- Log.w(VIEW_LOG_TAG, "couldn't find next focus view specified "
- + "by user for id " + childViewId);
+ Log.w(VIEW_LOG_TAG, "couldn't find view with id " + id);
}
return result;
}
@@ -6177,12 +6361,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (views == null) {
return;
}
- if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
- if (isAccessibilityFocusable()) {
- views.add(this);
- return;
- }
- }
if (!isFocusable()) {
return;
}
@@ -6257,7 +6435,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @return True if this View is accessibility focused.
*/
boolean isAccessibilityFocused() {
- return (mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0;
+ return (mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0;
}
/**
@@ -6282,8 +6460,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
- if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) == 0) {
- mPrivateFlags2 |= ACCESSIBILITY_FOCUSED;
+ if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) == 0) {
+ mPrivateFlags2 |= PFLAG2_ACCESSIBILITY_FOCUSED;
ViewRootImpl viewRootImpl = getViewRootImpl();
if (viewRootImpl != null) {
viewRootImpl.setAccessibilityFocus(this, null);
@@ -6305,8 +6483,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @hide
*/
public void clearAccessibilityFocus() {
- if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) {
- mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED;
+ if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) {
+ mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED;
invalidate();
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
notifyAccessibilityStateChanged();
@@ -6347,29 +6525,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
}
- private void requestAccessibilityFocusFromHover() {
- if (includeForAccessibility() && isActionableForAccessibility()) {
- requestAccessibilityFocus();
- } else {
- if (mParent != null) {
- View nextFocus = mParent.findViewToTakeAccessibilityFocusFromHover(this, this);
- if (nextFocus != null) {
- nextFocus.requestAccessibilityFocus();
- }
- }
- }
- }
-
- private boolean canTakeAccessibilityFocusFromHover() {
- if (includeForAccessibility() && isActionableForAccessibility()) {
- return true;
- }
- if (mParent != null) {
- return (mParent.findViewToTakeAccessibilityFocusFromHover(this, this) == this);
- }
- return false;
- }
-
/**
* Clears accessibility focus without calling any callback methods
* normally invoked in {@link #clearAccessibilityFocus()}. This method
@@ -6377,8 +6532,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* another view.
*/
void clearAccessibilityFocusNoCallbacks() {
- if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) {
- mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED;
+ if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) {
+ mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED;
invalidate();
}
}
@@ -6535,8 +6690,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
@ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO, to = "no")
})
public int getImportantForAccessibility() {
- return (mPrivateFlags2 & IMPORTANT_FOR_ACCESSIBILITY_MASK)
- >> IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+ return (mPrivateFlags2 & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK)
+ >> PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
}
/**
@@ -6554,9 +6709,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public void setImportantForAccessibility(int mode) {
if (mode != getImportantForAccessibility()) {
- mPrivateFlags2 &= ~IMPORTANT_FOR_ACCESSIBILITY_MASK;
- mPrivateFlags2 |= (mode << IMPORTANT_FOR_ACCESSIBILITY_SHIFT)
- & IMPORTANT_FOR_ACCESSIBILITY_MASK;
+ mPrivateFlags2 &= ~PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK;
+ mPrivateFlags2 |= (mode << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT)
+ & PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_MASK;
notifyAccessibilityStateChanged();
}
}
@@ -6569,15 +6724,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @hide
*/
public boolean isImportantForAccessibility() {
- final int mode = (mPrivateFlags2 & IMPORTANT_FOR_ACCESSIBILITY_MASK)
- >> IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+ 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:
return false;
case IMPORTANT_FOR_ACCESSIBILITY_AUTO:
- return isActionableForAccessibility() || hasListenersForAccessibility();
+ return isActionableForAccessibility() || hasListenersForAccessibility()
+ || getAccessibilityNodeProvider() != null;
default:
throw new IllegalArgumentException("Unknow important for accessibility mode: "
+ mode);
@@ -6585,73 +6741,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
- * Gets the mode for determining whether this View can take accessibility focus.
- *
- * @return The mode for determining whether a View can take accessibility focus.
- *
- * @attr ref android.R.styleable#View_accessibilityFocusable
- *
- * @see #ACCESSIBILITY_FOCUSABLE_YES
- * @see #ACCESSIBILITY_FOCUSABLE_NO
- * @see #ACCESSIBILITY_FOCUSABLE_AUTO
- *
- * @hide
- */
- @ViewDebug.ExportedProperty(category = "accessibility", mapping = {
- @ViewDebug.IntToString(from = ACCESSIBILITY_FOCUSABLE_AUTO, to = "auto"),
- @ViewDebug.IntToString(from = ACCESSIBILITY_FOCUSABLE_YES, to = "yes"),
- @ViewDebug.IntToString(from = ACCESSIBILITY_FOCUSABLE_NO, to = "no")
- })
- public int getAccessibilityFocusable() {
- return (mPrivateFlags2 & ACCESSIBILITY_FOCUSABLE_MASK) >>> ACCESSIBILITY_FOCUSABLE_SHIFT;
- }
-
- /**
- * Sets how to determine whether this view can take accessibility focus.
- *
- * @param mode How to determine whether this view can take accessibility focus.
- *
- * @attr ref android.R.styleable#View_accessibilityFocusable
- *
- * @see #ACCESSIBILITY_FOCUSABLE_YES
- * @see #ACCESSIBILITY_FOCUSABLE_NO
- * @see #ACCESSIBILITY_FOCUSABLE_AUTO
- *
- * @hide
- */
- public void setAccessibilityFocusable(int mode) {
- if (mode != getAccessibilityFocusable()) {
- mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSABLE_MASK;
- mPrivateFlags2 |= (mode << ACCESSIBILITY_FOCUSABLE_SHIFT)
- & ACCESSIBILITY_FOCUSABLE_MASK;
- notifyAccessibilityStateChanged();
- }
- }
-
- /**
- * Gets whether this view can take accessibility focus.
- *
- * @return Whether the view can take accessibility focus.
- *
- * @hide
- */
- public boolean isAccessibilityFocusable() {
- final int mode = (mPrivateFlags2 & ACCESSIBILITY_FOCUSABLE_MASK)
- >>> ACCESSIBILITY_FOCUSABLE_SHIFT;
- switch (mode) {
- case ACCESSIBILITY_FOCUSABLE_YES:
- return true;
- case ACCESSIBILITY_FOCUSABLE_NO:
- return false;
- case ACCESSIBILITY_FOCUSABLE_AUTO:
- return canTakeAccessibilityFocusFromHover()
- || getAccessibilityNodeProvider() != null;
- default:
- throw new IllegalArgumentException("Unknow accessibility focusable mode: " + mode);
- }
- }
-
- /**
* Gets the parent for accessibility purposes. Note that the parent for
* accessibility is not necessary the immediate parent. It is the first
* predecessor that is important for accessibility.
@@ -6696,10 +6785,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public boolean includeForAccessibility() {
if (mAttachInfo != null) {
- if (!mAttachInfo.mIncludeNotImportantViews) {
- return isImportantForAccessibility();
- }
- return true;
+ return mAttachInfo.mIncludeNotImportantViews || isImportantForAccessibility();
}
return false;
}
@@ -6707,7 +6793,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
/**
* Returns whether the View is considered actionable from
* accessibility perspective. Such view are important for
- * accessiiblity.
+ * accessibility.
*
* @return True if the view is actionable for accessibility.
*
@@ -6719,7 +6805,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
/**
* Returns whether the View has registered callbacks wich makes it
- * important for accessiiblity.
+ * important for accessibility.
*
* @return True if the view is actionable for accessibility.
*/
@@ -6748,8 +6834,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
return;
}
- if ((mPrivateFlags2 & ACCESSIBILITY_STATE_CHANGED) == 0) {
- mPrivateFlags2 |= ACCESSIBILITY_STATE_CHANGED;
+ if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_STATE_CHANGED) == 0) {
+ mPrivateFlags2 |= PFLAG2_ACCESSIBILITY_STATE_CHANGED;
if (mParent != null) {
mParent.childAccessibilityStateChanged(this);
}
@@ -6758,12 +6844,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
/**
* Reset the state indicating the this view has requested clients
- * interested in its accessiblity state to be notified.
+ * interested in its accessibility state to be notified.
*
* @hide
*/
public void resetAccessibilityStateChanged() {
- mPrivateFlags2 &= ~ACCESSIBILITY_STATE_CHANGED;
+ mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_STATE_CHANGED;
}
/**
@@ -6832,10 +6918,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
} break;
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
- final int mode = getAccessibilityFocusable();
- if (!isAccessibilityFocused()
- && (mode == ACCESSIBILITY_FOCUSABLE_YES
- || mode == ACCESSIBILITY_FOCUSABLE_AUTO)) {
+ if (!isAccessibilityFocused()) {
return requestAccessibilityFocus();
}
} break;
@@ -6929,7 +7012,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @hide
*/
public CharSequence getIterableTextForAccessibility() {
- return mContentDescription;
+ return getContentDescription();
}
/**
@@ -7018,7 +7101,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public void onStartTemporaryDetach() {
removeUnsetPressCallback();
- mPrivateFlags |= CANCEL_NEXT_UP_EVENT;
+ mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
}
/**
@@ -7337,13 +7420,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (isPressed()) {
setPressed(false);
}
- if (imm != null && (mPrivateFlags & FOCUSED) != 0) {
+ if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
imm.focusOut(this);
}
removeLongPressCallback();
removeTapCallback();
onFocusLost();
- } else if (imm != null && (mPrivateFlags & FOCUSED) != 0) {
+ } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
imm.focusIn(this);
}
refreshDrawableState();
@@ -7383,7 +7466,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mAttachInfo != null) {
initialAwakenScrollBars();
} else {
- mPrivateFlags |= AWAKEN_SCROLL_BARS_ON_ATTACH;
+ mPrivateFlags |= PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH;
}
}
}
@@ -7484,7 +7567,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
outRect.bottom -= insets.bottom;
return;
}
- Display d = WindowManagerImpl.getDefault().getDefaultDisplay();
+ // The view is not attached to a display so we don't have a context.
+ // Make a best guess about the display size.
+ Display d = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
d.getRectSize(outRect);
}
@@ -7537,7 +7622,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
void needGlobalAttributesUpdate(boolean force) {
final AttachInfo ai = mAttachInfo;
- if (ai != null) {
+ if (ai != null && !ai.mRecomputeGlobalAttributes) {
if (force || ai.mKeepScreenOn || (ai.mSystemUiVisibility != 0)
|| ai.mHasSystemUiListeners) {
ai.mRecomputeGlobalAttributes = true;
@@ -7983,7 +8068,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
@ViewDebug.ExportedProperty
public boolean isHovered() {
- return (mPrivateFlags & HOVERED) != 0;
+ return (mPrivateFlags & PFLAG_HOVERED) != 0;
}
/**
@@ -8003,14 +8088,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public void setHovered(boolean hovered) {
if (hovered) {
- if ((mPrivateFlags & HOVERED) == 0) {
- mPrivateFlags |= HOVERED;
+ if ((mPrivateFlags & PFLAG_HOVERED) == 0) {
+ mPrivateFlags |= PFLAG_HOVERED;
refreshDrawableState();
onHoverChanged(true);
}
} else {
- if ((mPrivateFlags & HOVERED) != 0) {
- mPrivateFlags &= ~HOVERED;
+ if ((mPrivateFlags & PFLAG_HOVERED) != 0) {
+ mPrivateFlags &= ~PFLAG_HOVERED;
refreshDrawableState();
onHoverChanged(false);
}
@@ -8042,7 +8127,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
- if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
+ if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
@@ -8061,8 +8146,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
- boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
- if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
+ boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
+ if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
@@ -8124,7 +8209,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
- mPrivateFlags |= PREPRESSED;
+ mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
@@ -8149,7 +8234,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
- if ((mPrivateFlags & PRESSED) != 0) {
+ if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
@@ -8200,7 +8285,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* Remove the prepress detection timer.
*/
private void removeUnsetPressCallback() {
- if ((mPrivateFlags & PRESSED) != 0 && mUnsetPressedState != null) {
+ if ((mPrivateFlags & PFLAG_PRESSED) != 0 && mUnsetPressedState != null) {
setPressed(false);
removeCallbacks(mUnsetPressedState);
}
@@ -8211,7 +8296,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
private void removeTapCallback() {
if (mPendingCheckForTap != null) {
- mPrivateFlags &= ~PREPRESSED;
+ mPrivateFlags &= ~PFLAG_PREPRESSED;
removeCallbacks(mPendingCheckForTap);
}
}
@@ -8276,13 +8361,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
/* Check if the FOCUSABLE bit has changed */
if (((changed & FOCUSABLE_MASK) != 0) &&
- ((privateFlags & HAS_BOUNDS) !=0)) {
+ ((privateFlags & PFLAG_HAS_BOUNDS) !=0)) {
if (((old & FOCUSABLE_MASK) == FOCUSABLE)
- && ((privateFlags & FOCUSED) != 0)) {
+ && ((privateFlags & PFLAG_FOCUSED) != 0)) {
/* Give up focus if we are no longer focusable */
clearFocus();
} else if (((old & FOCUSABLE_MASK) == NOT_FOCUSABLE)
- && ((privateFlags & FOCUSED) == 0)) {
+ && ((privateFlags & PFLAG_FOCUSED) == 0)) {
/*
* Tell the view system that we are now available to take focus
* if no one else already has it.
@@ -8301,7 +8386,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* it was not visible. Marking it drawn ensures that the invalidation will
* go through.
*/
- mPrivateFlags |= DRAWN;
+ mPrivateFlags |= PFLAG_DRAWN;
invalidate(true);
needGlobalAttributesUpdate(true);
@@ -8331,7 +8416,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
// Mark the view drawn to ensure that it gets invalidated properly the next
// time it is visible and gets invalidated
- mPrivateFlags |= DRAWN;
+ mPrivateFlags |= PFLAG_DRAWN;
}
if (mAttachInfo != null) {
mAttachInfo.mViewVisibilityChanged = true;
@@ -8345,7 +8430,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* If this view is becoming invisible, set the DRAWN flag so that
* the next invalidate() will not be skipped.
*/
- mPrivateFlags |= DRAWN;
+ mPrivateFlags |= PFLAG_DRAWN;
if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE) && hasFocus()) {
// root view becoming invisible shouldn't clear focus and accessibility focus
@@ -8376,25 +8461,25 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if ((changed & DRAWING_CACHE_ENABLED) != 0) {
destroyDrawingCache();
- mPrivateFlags &= ~DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
invalidateParentCaches();
}
if ((changed & DRAWING_CACHE_QUALITY_MASK) != 0) {
destroyDrawingCache();
- mPrivateFlags &= ~DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
if ((changed & DRAW_MASK) != 0) {
if ((mViewFlags & WILL_NOT_DRAW) != 0) {
if (mBackground != null) {
- mPrivateFlags &= ~SKIP_DRAW;
- mPrivateFlags |= ONLY_DRAWS_BACKGROUND;
+ mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+ mPrivateFlags |= PFLAG_ONLY_DRAWS_BACKGROUND;
} else {
- mPrivateFlags |= SKIP_DRAW;
+ mPrivateFlags |= PFLAG_SKIP_DRAW;
}
} else {
- mPrivateFlags &= ~SKIP_DRAW;
+ mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
requestLayout();
invalidate(true);
@@ -8702,7 +8787,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// asked for the matrix; recalculate it with the current values
// Figure out if we need to update the pivot point
- if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
+ if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
if ((mRight - mLeft) != info.mPrevWidth || (mBottom - mTop) != info.mPrevHeight) {
info.mPrevWidth = mRight - mLeft;
info.mPrevHeight = mBottom - mTop;
@@ -8736,18 +8821,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
}
- /**
- * When searching for a view to focus this rectangle is used when considering if this view is
- * a good candidate for receiving focus.
- *
- * By default, the rectangle is the {@link #getDrawingRect}) of the view.
- *
- * @param r The rectangle to fill in, in this view's coordinates.
- */
- public void getFocusRect(Rect r) {
- getDrawingRect(r);
- }
-
/**
* Utility method to retrieve the inverse of the current mMatrix property.
* We cache the matrix to avoid recalculating it when transform properties
@@ -8846,7 +8919,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mDisplayList != null) {
mDisplayList.setCameraDistance(-Math.abs(distance) / dpi);
}
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -8892,7 +8965,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mDisplayList != null) {
mDisplayList.setRotation(rotation);
}
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -8943,7 +9016,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mDisplayList != null) {
mDisplayList.setRotationY(rotationY);
}
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -8994,7 +9067,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mDisplayList != null) {
mDisplayList.setRotationX(rotationX);
}
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -9037,7 +9110,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mDisplayList != null) {
mDisplayList.setScaleX(scaleX);
}
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -9080,7 +9153,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mDisplayList != null) {
mDisplayList.setScaleY(scaleY);
}
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -9121,7 +9194,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public void setPivotX(float pivotX) {
ensureTransformationInfo();
- mPrivateFlags |= PIVOT_EXPLICITLY_SET;
+ mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET;
final TransformationInfo info = mTransformationInfo;
if (info.mPivotX != pivotX) {
invalidateViewProperty(true, false);
@@ -9131,7 +9204,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mDisplayList != null) {
mDisplayList.setPivotX(pivotX);
}
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -9171,7 +9244,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public void setPivotY(float pivotY) {
ensureTransformationInfo();
- mPrivateFlags |= PIVOT_EXPLICITLY_SET;
+ mPrivateFlags |= PFLAG_PIVOT_EXPLICITLY_SET;
final TransformationInfo info = mTransformationInfo;
if (info.mPivotY != pivotY) {
invalidateViewProperty(true, false);
@@ -9181,7 +9254,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mDisplayList != null) {
mDisplayList.setPivotY(pivotY);
}
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -9241,12 +9314,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mTransformationInfo.mAlpha != alpha) {
mTransformationInfo.mAlpha = alpha;
if (onSetAlpha((int) (alpha * 255))) {
- mPrivateFlags |= ALPHA_SET;
+ mPrivateFlags |= PFLAG_ALPHA_SET;
// subclass is handling alpha - don't optimize rendering cache invalidation
invalidateParentCaches();
invalidate(true);
} else {
- mPrivateFlags &= ~ALPHA_SET;
+ mPrivateFlags &= ~PFLAG_ALPHA_SET;
invalidateViewProperty(true, false);
if (mDisplayList != null) {
mDisplayList.setAlpha(alpha);
@@ -9271,10 +9344,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mTransformationInfo.mAlpha = alpha;
boolean subclassHandlesAlpha = onSetAlpha((int) (alpha * 255));
if (subclassHandlesAlpha) {
- mPrivateFlags |= ALPHA_SET;
+ mPrivateFlags |= PFLAG_ALPHA_SET;
return true;
} else {
- mPrivateFlags &= ~ALPHA_SET;
+ mPrivateFlags &= ~PFLAG_ALPHA_SET;
if (mDisplayList != null) {
mDisplayList.setAlpha(alpha);
}
@@ -9334,16 +9407,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
onSizeChanged(width, mBottom - mTop, width, oldHeight);
if (!matrixIsIdentity) {
- if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
+ if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
// A change in dimension means an auto-centered pivot point changes, too
mTransformationInfo.mMatrixDirty = true;
}
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+ mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
invalidate(true);
}
mBackgroundSizeChanged = true;
invalidateParentIfNeeded();
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -9366,7 +9439,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @return The dirty state of this view.
*/
public boolean isDirty() {
- return (mPrivateFlags & DIRTY_MASK) != 0;
+ return (mPrivateFlags & PFLAG_DIRTY_MASK) != 0;
}
/**
@@ -9407,16 +9480,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
onSizeChanged(width, mBottom - mTop, width, oldHeight);
if (!matrixIsIdentity) {
- if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
+ if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
// A change in dimension means an auto-centered pivot point changes, too
mTransformationInfo.mMatrixDirty = true;
}
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+ mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
invalidate(true);
}
mBackgroundSizeChanged = true;
invalidateParentIfNeeded();
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -9474,16 +9547,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
onSizeChanged(mRight - mLeft, height, oldWidth, height);
if (!matrixIsIdentity) {
- if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
+ if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
// A change in dimension means an auto-centered pivot point changes, too
mTransformationInfo.mMatrixDirty = true;
}
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+ mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
invalidate(true);
}
mBackgroundSizeChanged = true;
invalidateParentIfNeeded();
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -9538,16 +9611,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
onSizeChanged(mRight - mLeft, height, oldWidth, height);
if (!matrixIsIdentity) {
- if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
+ if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
// A change in dimension means an auto-centered pivot point changes, too
mTransformationInfo.mMatrixDirty = true;
}
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+ mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
invalidate(true);
}
mBackgroundSizeChanged = true;
invalidateParentIfNeeded();
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -9635,7 +9708,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mDisplayList != null) {
mDisplayList.setTranslationX(translationX);
}
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -9676,7 +9749,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mDisplayList != null) {
mDisplayList.setTranslationY(translationY);
}
- if ((mPrivateFlags2 & VIEW_QUICK_REJECTED) == VIEW_QUICK_REJECTED) {
+ if ((mPrivateFlags2 & PFLAG2_VIEW_QUICK_REJECTED) == PFLAG2_VIEW_QUICK_REJECTED) {
// View was rejected last time it was drawn by its parent; this may have changed
invalidateParentIfNeeded();
}
@@ -9726,7 +9799,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* When a view has focus and the user navigates away from it, the next view is searched for
* starting from the rectangle filled in by this method.
*
- * By default, the rectange is the {@link #getDrawingRect(android.graphics.Rect)})
+ * By default, the rectangle is the {@link #getDrawingRect(android.graphics.Rect)})
* of the view. However, if your view maintains some idea of internal selection,
* such as a cursor, or a selected row or column, you should override this method and
* fill in a more specific rectangle.
@@ -9910,6 +9983,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
throw new NullPointerException("Layout parameters cannot be null");
}
mLayoutParams = params;
+ resolveLayoutParams();
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).onSetLayoutParams(this, params);
}
@@ -9917,6 +9991,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Resolve the layout parameters depending on the resolved layout direction
+ */
+ private void resolveLayoutParams() {
+ if (mLayoutParams != null) {
+ mLayoutParams.onResolveLayoutDirection(getLayoutDirection());
+ }
+ }
+
+ /**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
@@ -10137,12 +10220,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (skipInvalidate()) {
return;
}
- if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
- (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID ||
- (mPrivateFlags & INVALIDATED) != INVALIDATED) {
- mPrivateFlags &= ~DRAWING_CACHE_VALID;
- mPrivateFlags |= INVALIDATED;
- mPrivateFlags |= DIRTY;
+ if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
+ (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID ||
+ (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED) {
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+ mPrivateFlags |= PFLAG_INVALIDATED;
+ mPrivateFlags |= PFLAG_DIRTY;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
//noinspection PointlessBooleanExpression,ConstantConditions
@@ -10180,12 +10263,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (skipInvalidate()) {
return;
}
- if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
- (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID ||
- (mPrivateFlags & INVALIDATED) != INVALIDATED) {
- mPrivateFlags &= ~DRAWING_CACHE_VALID;
- mPrivateFlags |= INVALIDATED;
- mPrivateFlags |= DIRTY;
+ if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
+ (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID ||
+ (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED) {
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
+ mPrivateFlags |= PFLAG_INVALIDATED;
+ mPrivateFlags |= PFLAG_DIRTY;
final ViewParent p = mParent;
final AttachInfo ai = mAttachInfo;
//noinspection PointlessBooleanExpression,ConstantConditions
@@ -10232,15 +10315,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (skipInvalidate()) {
return;
}
- if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS) ||
- (invalidateCache && (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) ||
- (mPrivateFlags & INVALIDATED) != INVALIDATED || isOpaque() != mLastIsOpaque) {
+ if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
+ (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) ||
+ (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) {
mLastIsOpaque = isOpaque();
- mPrivateFlags &= ~DRAWN;
- mPrivateFlags |= DIRTY;
+ mPrivateFlags &= ~PFLAG_DRAWN;
+ mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
- mPrivateFlags |= INVALIDATED;
- mPrivateFlags &= ~DRAWING_CACHE_VALID;
+ mPrivateFlags |= PFLAG_INVALIDATED;
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
@@ -10281,12 +10364,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* list properties are not being used in this view
*/
void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
- if (mDisplayList == null || (mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION) {
+ if (mDisplayList == null || (mPrivateFlags & PFLAG_DRAW_ANIMATION) == PFLAG_DRAW_ANIMATION) {
if (invalidateParent) {
invalidateParentCaches();
}
if (forceRedraw) {
- mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+ mPrivateFlags |= PFLAG_DRAWN; // force another invalidation with the new orientation
}
invalidate(false);
} else {
@@ -10330,7 +10413,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
protected void invalidateParentCaches() {
if (mParent instanceof View) {
- ((View) mParent).mPrivateFlags |= INVALIDATED;
+ ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
}
}
@@ -10362,9 +10445,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
@ViewDebug.ExportedProperty(category = "drawing")
public boolean isOpaque() {
- return (mPrivateFlags & OPAQUE_MASK) == OPAQUE_MASK &&
- ((mTransformationInfo != null ? mTransformationInfo.mAlpha : 1)
- >= 1.0f - ViewConfiguration.ALPHA_THRESHOLD);
+ return (mPrivateFlags & PFLAG_OPAQUE_MASK) == PFLAG_OPAQUE_MASK &&
+ ((mTransformationInfo != null ? mTransformationInfo.mAlpha : 1.0f) >= 1.0f);
}
/**
@@ -10377,17 +10459,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// - Doesn't have scrollbars or scrollbars are inside overlay
if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
- mPrivateFlags |= OPAQUE_BACKGROUND;
+ mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
} else {
- mPrivateFlags &= ~OPAQUE_BACKGROUND;
+ mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;
}
final int flags = mViewFlags;
if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
(flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY) {
- mPrivateFlags |= OPAQUE_SCROLLBARS;
+ mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
} else {
- mPrivateFlags &= ~OPAQUE_SCROLLBARS;
+ mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;
}
}
@@ -10395,7 +10477,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @hide
*/
protected boolean hasOpaqueScrollbars() {
- return (mPrivateFlags & OPAQUE_SCROLLBARS) == OPAQUE_SCROLLBARS;
+ return (mPrivateFlags & PFLAG_OPAQUE_SCROLLBARS) == PFLAG_OPAQUE_SCROLLBARS;
}
/**
@@ -10923,7 +11005,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @hide
*/
protected void recomputePadding() {
- setPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom);
+ internalSetPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom);
}
/**
@@ -11331,9 +11413,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
scrollBar.setParameters(computeVerticalScrollRange(),
computeVerticalScrollOffset(),
computeVerticalScrollExtent(), true);
- switch (mVerticalScrollbarPosition) {
+ int verticalScrollbarPosition = mVerticalScrollbarPosition;
+ if (verticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT) {
+ verticalScrollbarPosition = isLayoutRtl() ?
+ SCROLLBAR_POSITION_LEFT : SCROLLBAR_POSITION_RIGHT;
+ }
+ switch (verticalScrollbarPosition) {
default:
- case SCROLLBAR_POSITION_DEFAULT:
case SCROLLBAR_POSITION_RIGHT:
left = scrollX + width - size - (mUserPaddingRight & inside);
break;
@@ -11436,24 +11522,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #onDetachedFromWindow()
*/
protected void onAttachedToWindow() {
- if ((mPrivateFlags & REQUEST_TRANSPARENT_REGIONS) != 0) {
+ if ((mPrivateFlags & PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
mParent.requestTransparentRegion(this);
}
- if ((mPrivateFlags & AWAKEN_SCROLL_BARS_ON_ATTACH) != 0) {
+ if ((mPrivateFlags & PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH) != 0) {
initialAwakenScrollBars();
- mPrivateFlags &= ~AWAKEN_SCROLL_BARS_ON_ATTACH;
+ mPrivateFlags &= ~PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH;
}
jumpDrawablesToCurrentState();
- // Order is important here: LayoutDirection MUST be resolved before Padding
- // and TextDirection
- resolveLayoutDirection();
- resolvePadding();
- resolveTextDirection();
- resolveTextAlignment();
-
clearAccessibilityFocus();
if (isFocused()) {
InputMethodManager imm = InputMethodManager.peekInstance();
@@ -11466,6 +11545,44 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Resolve all RTL related properties.
+ */
+ void resolveRtlPropertiesIfNeeded() {
+ if (!needRtlPropertiesResolution()) return;
+
+ // Order is important here: LayoutDirection MUST be resolved first
+ if (!isLayoutDirectionResolved()) {
+ resolveLayoutDirection();
+ resolveLayoutParams();
+ }
+ // ... then we can resolve the others properties depending on the resolved LayoutDirection.
+ if (!isTextDirectionResolved()) {
+ resolveTextDirection();
+ }
+ if (!isTextAlignmentResolved()) {
+ resolveTextAlignment();
+ }
+ if (!isPaddingResolved()) {
+ resolvePadding();
+ }
+ if (!isDrawablesResolved()) {
+ resolveDrawables();
+ }
+ requestLayout();
+ invalidate(true);
+ onRtlPropertiesChanged(getLayoutDirection());
+ }
+
+ // Reset resolution of all RTL related properties.
+ void resetRtlProperties() {
+ resetResolvedLayoutDirection();
+ resetResolvedTextDirection();
+ resetResolvedTextAlignment();
+ resetResolvedPadding();
+ resetResolvedDrawables();
+ }
+
+ /**
* @see #onScreenStateChanged(int)
*/
void dispatchScreenStateChanged(int screenState) {
@@ -11492,36 +11609,74 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Return true if we are in RTL compatibility mode (either before Jelly Bean MR1 or
+ * RTL not supported)
+ */
+ private boolean isRtlCompatibilityMode() {
+ final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+ return targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport();
+ }
+
+ /**
+ * @return true if RTL properties need resolution.
+ */
+ private boolean needRtlPropertiesResolution() {
+ return (mPrivateFlags2 & ALL_RTL_PROPERTIES_RESOLVED) != ALL_RTL_PROPERTIES_RESOLVED;
+ }
+
+ /**
+ * Called when any RTL property (layout direction or text direction or text alignment) has
+ * been changed.
+ *
+ * Subclasses need to override this method to take care of cached information that depends on the
+ * resolved layout direction, or to inform child views that inherit their layout direction.
+ *
+ * The default implementation does nothing.
+ *
+ * @param layoutDirection the direction of the layout
+ *
+ * @see #LAYOUT_DIRECTION_LTR
+ * @see #LAYOUT_DIRECTION_RTL
+ */
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ }
+
+ /**
* Resolve and cache the layout direction. LTR is set initially. This is implicitly supposing
* that the parent directionality can and will be resolved before its children.
- * Will call {@link View#onResolvedLayoutDirectionChanged} when resolution is done.
+ *
+ * @return true if resolution has been done, false otherwise.
+ *
* @hide
*/
- public void resolveLayoutDirection() {
+ public boolean resolveLayoutDirection() {
// Clear any previous layout direction resolution
- mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED_MASK;
+ mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK;
if (hasRtlSupport()) {
// Set resolved depending on layout direction
- switch (getLayoutDirection()) {
+ switch ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >>
+ PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) {
case LAYOUT_DIRECTION_INHERIT:
- // If this is root view, no need to look at parent's layout dir.
- if (canResolveLayoutDirection()) {
- ViewGroup viewGroup = ((ViewGroup) mParent);
+ // We cannot resolve yet. LTR is by default and let the resolution happen again
+ // later to get the correct resolved value
+ if (!canResolveLayoutDirection()) return false;
- if (viewGroup.getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) {
- mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED_RTL;
- }
- } else {
- // Nothing to do, LTR by default
+ View parent = ((View) mParent);
+ // Parent has not yet resolved, LTR is still the default
+ if (!parent.isLayoutDirectionResolved()) return false;
+
+ if (parent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
}
break;
case LAYOUT_DIRECTION_RTL:
- mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED_RTL;
+ mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
break;
case LAYOUT_DIRECTION_LOCALE:
- if(isLayoutDirectionRtl(Locale.getDefault())) {
- mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED_RTL;
+ if((LAYOUT_DIRECTION_RTL ==
+ TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()))) {
+ mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
}
break;
default:
@@ -11530,137 +11685,115 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
// Set to resolved
- mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED;
- onResolvedLayoutDirectionChanged();
- // Resolve padding
- resolvePadding();
+ mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+ return true;
}
/**
- * Called when layout direction has been resolved.
+ * Check if layout direction resolution can be done.
+ *
+ * @return true if layout direction resolution can be done otherwise return false.
*
- * The default implementation does nothing.
* @hide
*/
- public void onResolvedLayoutDirectionChanged() {
+ public boolean canResolveLayoutDirection() {
+ switch (getRawLayoutDirection()) {
+ case LAYOUT_DIRECTION_INHERIT:
+ return (mParent != null) && (mParent instanceof ViewGroup) &&
+ ((ViewGroup) mParent).canResolveLayoutDirection();
+ default:
+ return true;
+ }
}
/**
- * Resolve padding depending on layout direction.
+ * Reset the resolved layout direction. Layout direction will be resolved during a call to
+ * {@link #onMeasure(int, int)}.
+ *
* @hide
*/
- public void resolvePadding() {
- // If the user specified the absolute padding (either with android:padding or
- // android:paddingLeft/Top/Right/Bottom), use this padding, otherwise
- // use the default padding or the padding from the background drawable
- // (stored at this point in mPadding*)
- int resolvedLayoutDirection = getResolvedLayoutDirection();
- switch (resolvedLayoutDirection) {
- case LAYOUT_DIRECTION_RTL:
- // Start user padding override Right user padding. Otherwise, if Right user
- // padding is not defined, use the default Right padding. If Right user padding
- // is defined, just use it.
- if (mUserPaddingStart >= 0) {
- mUserPaddingRight = mUserPaddingStart;
- } else if (mUserPaddingRight < 0) {
- mUserPaddingRight = mPaddingRight;
- }
- if (mUserPaddingEnd >= 0) {
- mUserPaddingLeft = mUserPaddingEnd;
- } else if (mUserPaddingLeft < 0) {
- mUserPaddingLeft = mPaddingLeft;
- }
- break;
- case LAYOUT_DIRECTION_LTR:
- default:
- // Start user padding override Left user padding. Otherwise, if Left user
- // padding is not defined, use the default left padding. If Left user padding
- // is defined, just use it.
- if (mUserPaddingStart >= 0) {
- mUserPaddingLeft = mUserPaddingStart;
- } else if (mUserPaddingLeft < 0) {
- mUserPaddingLeft = mPaddingLeft;
- }
- if (mUserPaddingEnd >= 0) {
- mUserPaddingRight = mUserPaddingEnd;
- } else if (mUserPaddingRight < 0) {
- mUserPaddingRight = mPaddingRight;
- }
- }
-
- mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom;
-
- if(isPaddingRelative()) {
- setPaddingRelative(mUserPaddingStart, mPaddingTop, mUserPaddingEnd, mUserPaddingBottom);
- } else {
- recomputePadding();
- }
- onPaddingChanged(resolvedLayoutDirection);
+ public void resetResolvedLayoutDirection() {
+ // Reset the current resolved bits
+ mPrivateFlags2 &= ~PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK;
}
/**
- * Resolve padding depending on the layout direction. Subclasses that care about
- * padding resolution should override this method. The default implementation does
- * nothing.
- *
- * @param layoutDirection the direction of the layout
+ * @return true if the layout direction is inherited.
*
- * @see {@link #LAYOUT_DIRECTION_LTR}
- * @see {@link #LAYOUT_DIRECTION_RTL}
* @hide
*/
- public void onPaddingChanged(int layoutDirection) {
+ public boolean isLayoutDirectionInherited() {
+ return (getRawLayoutDirection() == LAYOUT_DIRECTION_INHERIT);
}
/**
- * Check if layout direction resolution can be done.
- *
- * @return true if layout direction resolution can be done otherwise return false.
- * @hide
+ * @return true if layout direction has been resolved.
*/
- public boolean canResolveLayoutDirection() {
- switch (getLayoutDirection()) {
- case LAYOUT_DIRECTION_INHERIT:
- return (mParent != null) && (mParent instanceof ViewGroup);
- default:
- return true;
- }
+ private boolean isLayoutDirectionResolved() {
+ return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED) == PFLAG2_LAYOUT_DIRECTION_RESOLVED;
}
/**
- * Reset the resolved layout direction. Will call {@link View#onResolvedLayoutDirectionReset}
- * when reset is done.
+ * Return if padding has been resolved
+ *
* @hide
*/
- public void resetResolvedLayoutDirection() {
- // Reset the current resolved bits
- mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED_MASK;
- onResolvedLayoutDirectionReset();
- // Reset also the text direction
- resetResolvedTextDirection();
+ boolean isPaddingResolved() {
+ return (mPrivateFlags2 & PFLAG2_PADDING_RESOLVED) == PFLAG2_PADDING_RESOLVED;
}
/**
- * Called during reset of resolved layout direction.
- *
- * Subclasses need to override this method to clear cached information that depends on the
- * resolved layout direction, or to inform child views that inherit their layout direction.
+ * Resolve padding depending on layout direction.
*
- * The default implementation does nothing.
* @hide
*/
- public void onResolvedLayoutDirectionReset() {
+ public void resolvePadding() {
+ 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();
+ // Set user padding to initial values ...
+ mUserPaddingLeft = mUserPaddingLeftInitial;
+ mUserPaddingRight = mUserPaddingRightInitial;
+ // ... then resolve it.
+ switch (resolvedLayoutDirection) {
+ case LAYOUT_DIRECTION_RTL:
+ if (mUserPaddingStart != UNDEFINED_PADDING) {
+ mUserPaddingRight = mUserPaddingStart;
+ }
+ if (mUserPaddingEnd != UNDEFINED_PADDING) {
+ mUserPaddingLeft = mUserPaddingEnd;
+ }
+ break;
+ case LAYOUT_DIRECTION_LTR:
+ default:
+ if (mUserPaddingStart != UNDEFINED_PADDING) {
+ mUserPaddingLeft = mUserPaddingStart;
+ }
+ if (mUserPaddingEnd != UNDEFINED_PADDING) {
+ mUserPaddingRight = mUserPaddingEnd;
+ }
+ }
+
+ mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom;
+
+ internalSetPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight,
+ mUserPaddingBottom);
+ onRtlPropertiesChanged(resolvedLayoutDirection);
+ }
+
+ mPrivateFlags2 |= PFLAG2_PADDING_RESOLVED;
}
/**
- * Check if a Locale uses an RTL script.
+ * Reset the resolved layout direction.
*
- * @param locale Locale to check
- * @return true if the Locale uses an RTL script.
* @hide
*/
- protected static boolean isLayoutDirectionRtl(Locale locale) {
- return (LAYOUT_DIRECTION_RTL == LocaleUtil.getLayoutDirectionFromLocale(locale));
+ public void resetResolvedPadding() {
+ mPrivateFlags2 &= ~PFLAG2_PADDING_RESOLVED;
}
/**
@@ -11670,7 +11803,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #onAttachedToWindow()
*/
protected void onDetachedFromWindow() {
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
+ mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
removeUnsetPressCallback();
removeLongPressCallback();
@@ -11693,8 +11826,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mCurrentAnimation = null;
- resetResolvedLayoutDirection();
- resetResolvedTextAlignment();
+ resetRtlProperties();
+ onRtlPropertiesChanged(LAYOUT_DIRECTION_DEFAULT);
resetAccessibilityStateChanged();
}
@@ -11737,6 +11870,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Gets the logical display to which the view's window has been attached.
+ *
+ * @return The logical display, or null if the view is not currently attached to a window.
+ */
+ public Display getDisplay() {
+ return mAttachInfo != null ? mAttachInfo.mDisplay : null;
+ }
+
+ /**
* Retrieve private session object this view hierarchy is using to
* communicate with the window manager.
* @return the session object to communicate with the window manager
@@ -11754,14 +11896,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mAttachInfo = info;
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
- mPrivateFlags |= DRAWABLE_STATE_DIRTY;
+ mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
- if ((mPrivateFlags&SCROLL_CONTAINER) != 0) {
+ if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
- mPrivateFlags |= SCROLL_CONTAINER_ADDED;
+ mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
@@ -11783,10 +11925,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (vis != GONE) {
onWindowVisibilityChanged(vis);
}
- if ((mPrivateFlags&DRAWABLE_STATE_DIRTY) != 0) {
+ if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
// If nobody has evaluated the drawable state yet, then do it now.
refreshDrawableState();
}
+ needGlobalAttributesUpdate(false);
}
void dispatchDetachedFromWindow() {
@@ -11813,9 +11956,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
}
- if ((mPrivateFlags & SCROLL_CONTAINER_ADDED) != 0) {
+ if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
mAttachInfo.mScrollContainers.remove(this);
- mPrivateFlags &= ~SCROLL_CONTAINER_ADDED;
+ mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
}
mAttachInfo = null;
@@ -11847,9 +11990,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
- mPrivateFlags &= ~SAVE_STATE_CALLED;
+ mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
Parcelable state = onSaveInstanceState();
- if ((mPrivateFlags & SAVE_STATE_CALLED) == 0) {
+ if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onSaveInstanceState()");
}
@@ -11883,7 +12026,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #setSaveEnabled(boolean)
*/
protected Parcelable onSaveInstanceState() {
- mPrivateFlags |= SAVE_STATE_CALLED;
+ mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
return BaseSavedState.EMPTY_STATE;
}
@@ -11918,9 +12061,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (state != null) {
// Log.i("View", "Restoreing #" + Integer.toHexString(mID)
// + ": " + state);
- mPrivateFlags &= ~SAVE_STATE_CALLED;
+ mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
onRestoreInstanceState(state);
- if ((mPrivateFlags & SAVE_STATE_CALLED) == 0) {
+ if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onRestoreInstanceState()");
}
@@ -11941,7 +12084,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #dispatchRestoreInstanceState(android.util.SparseArray)
*/
protected void onRestoreInstanceState(Parcelable state) {
- mPrivateFlags |= SAVE_STATE_CALLED;
+ mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
if (state != BaseSavedState.EMPTY_STATE && state != null) {
throw new IllegalArgumentException("Wrong state class, expecting View State but "
+ "received " + state.getClass().toString() + " instead. This usually happens "
@@ -12016,13 +12159,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@link #setAlpha(float)}, the alpha value of the layer's paint is replaced by
* this view's alpha value. Calling {@link #setAlpha(float)} is therefore
* equivalent to setting a hardware layer on this view and providing a paint with
- * the desired alpha value.<p>
+ * the desired alpha value.</p>
*
* <p>Refer to the documentation of {@link #LAYER_TYPE_NONE disabled},
* {@link #LAYER_TYPE_SOFTWARE software} and {@link #LAYER_TYPE_HARDWARE hardware}
* for more information on when and how to use layers.</p>
*
- * @param layerType The ype of layer to use with this view, must be one of
+ * @param layerType The type of layer to use with this view, must be one of
* {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
* {@link #LAYER_TYPE_HARDWARE}
* @param paint The paint used to compose the layer. This argument is optional
@@ -12074,6 +12217,50 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Updates the {@link Paint} object used with the current layer (used only if the current
+ * layer type is not set to {@link #LAYER_TYPE_NONE}). Changed properties of the Paint
+ * provided to {@link #setLayerType(int, android.graphics.Paint)} will be used the next time
+ * the View is redrawn, but {@link #setLayerPaint(android.graphics.Paint)} must be called to
+ * ensure that the view gets redrawn immediately.
+ *
+ * <p>A layer is associated with an optional {@link android.graphics.Paint}
+ * instance that controls how the layer is composed on screen. The following
+ * properties of the paint are taken into account when composing the layer:</p>
+ * <ul>
+ * <li>{@link android.graphics.Paint#getAlpha() Translucency (alpha)}</li>
+ * <li>{@link android.graphics.Paint#getXfermode() Blending mode}</li>
+ * <li>{@link android.graphics.Paint#getColorFilter() Color filter}</li>
+ * </ul>
+ *
+ * <p>If this view has an alpha value set to < 1.0 by calling
+ * {@link #setAlpha(float)}, the alpha value of the layer's paint is replaced by
+ * this view's alpha value. Calling {@link #setAlpha(float)} is therefore
+ * equivalent to setting a hardware layer on this view and providing a paint with
+ * the desired alpha value.</p>
+ *
+ * @param paint The paint used to compose the layer. This argument is optional
+ * and can be null. It is ignored when the layer type is
+ * {@link #LAYER_TYPE_NONE}
+ *
+ * @see #setLayerType(int, android.graphics.Paint)
+ */
+ public void setLayerPaint(Paint paint) {
+ int layerType = getLayerType();
+ if (layerType != LAYER_TYPE_NONE) {
+ mLayerPaint = paint == null ? new Paint() : paint;
+ if (layerType == LAYER_TYPE_HARDWARE) {
+ HardwareLayer layer = getHardwareLayer();
+ if (layer != null) {
+ layer.setLayerPaint(paint);
+ }
+ invalidateViewProperty(false, false);
+ } else {
+ invalidate();
+ }
+ }
+ }
+
+ /**
* Indicates whether this view has a static layer. A view with layer type
* {@link #LAYER_TYPE_NONE} is a static layer. Other types of layers are
* dynamic.
@@ -12135,13 +12322,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
}
- // Make sure the HardwareRenderer.validate() was invoked before calling this method
- void flushLayer() {
- if (mLayerType == LAYER_TYPE_HARDWARE && mHardwareLayer != null) {
- mHardwareLayer.flush();
- }
- }
-
/**
* <p>Returns a hardware layer that can be used to draw this view again
* without executing its draw method.</p>
@@ -12163,14 +12343,30 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
return null;
}
- if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mHardwareLayer == null) {
+ if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || mHardwareLayer == null) {
if (mHardwareLayer == null) {
mHardwareLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
width, height, isOpaque());
mLocalDirtyRect.set(0, 0, width, height);
- } else if (mHardwareLayer.getWidth() != width || mHardwareLayer.getHeight() != height) {
- mHardwareLayer.resize(width, height);
- mLocalDirtyRect.set(0, 0, width, height);
+ } else {
+ if (mHardwareLayer.getWidth() != width || mHardwareLayer.getHeight() != height) {
+ if (mHardwareLayer.resize(width, height)) {
+ mLocalDirtyRect.set(0, 0, width, height);
+ }
+ }
+
+ // This should not be necessary but applications that change
+ // the parameters of their background drawable without calling
+ // this.setBackground(Drawable) can leave the view in a bad state
+ // (for instance isOpaque() returns true, but the background is
+ // not opaque.)
+ computeOpaqueFlags();
+
+ final boolean opaque = isOpaque();
+ if (mHardwareLayer.isValid() && mHardwareLayer.isOpaque() != opaque) {
+ mHardwareLayer.setOpaque(opaque);
+ mLocalDirtyRect.set(0, 0, width, height);
+ }
}
// The layer is not valid if the underlying GPU resources cannot be allocated
@@ -12178,7 +12374,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
return null;
}
- mHardwareLayer.redraw(getHardwareLayerDisplayList(mHardwareLayer), mLocalDirtyRect);
+ mHardwareLayer.setLayerPaint(mLayerPaint);
+ mHardwareLayer.redrawLater(getHardwareLayerDisplayList(mHardwareLayer), mLocalDirtyRect);
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null) viewRoot.pushHardwareLayerUpdate(mHardwareLayer);
+
mLocalDirtyRect.setEmpty();
}
@@ -12202,6 +12402,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mHardwareLayer.destroy();
mHardwareLayer = null;
+ if (mDisplayList != null) {
+ mDisplayList.reset();
+ }
invalidate(true);
invalidateParentCaches();
}
@@ -12278,10 +12481,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
@SuppressWarnings({"UnusedDeclaration"})
public void outputDirtyFlags(String indent, boolean clear, int clearMask) {
- Log.d("View", indent + this + " DIRTY(" + (mPrivateFlags & View.DIRTY_MASK) +
- ") DRAWN(" + (mPrivateFlags & DRAWN) + ")" + " CACHE_VALID(" +
- (mPrivateFlags & View.DRAWING_CACHE_VALID) +
- ") INVALIDATED(" + (mPrivateFlags & INVALIDATED) + ")");
+ Log.d("View", indent + this + " DIRTY(" + (mPrivateFlags & View.PFLAG_DIRTY_MASK) +
+ ") DRAWN(" + (mPrivateFlags & PFLAG_DRAWN) + ")" + " CACHE_VALID(" +
+ (mPrivateFlags & View.PFLAG_DRAWING_CACHE_VALID) +
+ ") INVALIDATED(" + (mPrivateFlags & PFLAG_INVALIDATED) + ")");
if (clear) {
mPrivateFlags &= clearMask;
}
@@ -12345,15 +12548,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
return null;
}
- if (((mPrivateFlags & DRAWING_CACHE_VALID) == 0 ||
+ if (((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 ||
displayList == null || !displayList.isValid() ||
(!isLayer && mRecreateDisplayList))) {
// Don't need to recreate the display list, just need to tell our
// children to restore/recreate theirs
if (displayList != null && displayList.isValid() &&
!isLayer && !mRecreateDisplayList) {
- mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
- mPrivateFlags &= ~DIRTY_MASK;
+ mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
return displayList;
@@ -12382,9 +12585,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
canvas.setViewport(width, height);
// The dirty rect should always be null for a display list
canvas.onPreDraw(null);
- int layerType = (
- !(mParent instanceof ViewGroup) || ((ViewGroup)mParent).mDrawLayers) ?
- getLayerType() : LAYER_TYPE_NONE;
+ int layerType = getLayerType();
if (!isLayer && layerType != LAYER_TYPE_NONE) {
if (layerType == LAYER_TYPE_HARDWARE) {
final HardwareLayer layer = getHardwareLayer();
@@ -12410,12 +12611,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
canvas.translate(-mScrollX, -mScrollY);
if (!isLayer) {
- mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
- mPrivateFlags &= ~DIRTY_MASK;
+ mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
// Fast path for layouts with no backgrounds
- if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
} else {
draw(canvas);
@@ -12433,8 +12634,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
}
} else if (!isLayer) {
- mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
- mPrivateFlags &= ~DIRTY_MASK;
+ mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return displayList;
@@ -12556,7 +12757,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
public void setDrawingCacheBackgroundColor(int color) {
if (color != mDrawingCacheBackgroundColor) {
mDrawingCacheBackgroundColor = color;
- mPrivateFlags &= ~DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
}
@@ -12602,7 +12803,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #destroyDrawingCache()
*/
public void buildDrawingCache(boolean autoScale) {
- if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || (autoScale ?
+ if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?
mDrawingCache == null : mUnscaledDrawingCache == null)) {
mCachingFailed = false;
@@ -12621,10 +12822,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
final boolean opaque = drawingCacheBackgroundColor != 0 || isOpaque();
final boolean use32BitCache = attachInfo != null && attachInfo.mUse32BitDrawingCache;
- if (width <= 0 || height <= 0 ||
- // Projected bitmap size in bytes
- (width * height * (opaque && !use32BitCache ? 2 : 4) >
- ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize())) {
+ final long projectedBitmapSize = width * height * (opaque && !use32BitCache ? 2 : 4);
+ final long drawingCacheSize =
+ ViewConfiguration.get(mContext).getScaledMaximumDrawingCacheSize();
+ if (width <= 0 || height <= 0 || projectedBitmapSize > drawingCacheSize) {
+ if (width > 0 && height > 0) {
+ Log.w(VIEW_LOG_TAG, "View too large to fit into drawing cache, needs "
+ + projectedBitmapSize + " bytes, only "
+ + drawingCacheSize + " available");
+ }
destroyDrawingCache();
mCachingFailed = true;
return;
@@ -12662,7 +12868,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (bitmap != null) bitmap.recycle();
try {
- bitmap = Bitmap.createBitmap(width, height, quality);
+ bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
+ width, height, quality);
bitmap.setDensity(getResources().getDisplayMetrics().densityDpi);
if (autoScale) {
mDrawingCache = bitmap;
@@ -12717,15 +12924,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
canvas.translate(-mScrollX, -mScrollY);
- mPrivateFlags |= DRAWN;
+ mPrivateFlags |= PFLAG_DRAWN;
if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||
mLayerType != LAYER_TYPE_NONE) {
- mPrivateFlags |= DRAWING_CACHE_VALID;
+ mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;
}
// Fast path for layouts with no backgrounds
- if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
- mPrivateFlags &= ~DIRTY_MASK;
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
@@ -12754,7 +12961,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
width = (int) ((width * scale) + 0.5f);
height = (int) ((height * scale) + 0.5f);
- Bitmap bitmap = Bitmap.createBitmap(width > 0 ? width : 1, height > 0 ? height : 1, quality);
+ Bitmap bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(),
+ width > 0 ? width : 1, height > 0 ? height : 1, quality);
if (bitmap == null) {
throw new OutOfMemoryError();
}
@@ -12792,10 +13000,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// Temporarily remove the dirty mask
int flags = mPrivateFlags;
- mPrivateFlags &= ~DIRTY_MASK;
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
// Fast path for layouts with no backgrounds
- if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
} else {
draw(canvas);
@@ -12979,13 +13187,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (more) {
if (!a.willChangeBounds()) {
- if ((flags & (parent.FLAG_OPTIMIZE_INVALIDATE | parent.FLAG_ANIMATION_DONE)) ==
- parent.FLAG_OPTIMIZE_INVALIDATE) {
- parent.mGroupFlags |= parent.FLAG_INVALIDATE_REQUIRED;
- } else if ((flags & parent.FLAG_INVALIDATE_REQUIRED) == 0) {
+ if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
+ ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
+ parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
+ } else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
- parent.mPrivateFlags |= DRAW_ANIMATION;
+ parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
parent.invalidate(mLeft, mTop, mRight, mBottom);
}
} else {
@@ -12998,7 +13206,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
- parent.mPrivateFlags |= DRAW_ANIMATION;
+ parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
final int left = mLeft + (int) region.left;
final int top = mTop + (int) region.top;
@@ -13060,7 +13268,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mTransformationInfo.matrix3D = new Matrix();
}
displayList.setCameraDistance(mTransformationInfo.mCamera.getLocationZ());
- if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == PIVOT_EXPLICITLY_SET) {
+ if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == PFLAG_PIVOT_EXPLICITLY_SET) {
displayList.setPivotX(getPivotX());
displayList.setPivotY(getPivotY());
}
@@ -13091,7 +13299,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
boolean scalingRequired = false;
boolean caching;
- int layerType = parent.mDrawLayers ? getLayerType() : LAYER_TYPE_NONE;
+ int layerType = getLayerType();
final boolean hardwareAccelerated = canvas.isHardwareAccelerated();
if ((flags & ViewGroup.FLAG_CHILDREN_DRAWN_WITH_CACHE) != 0 ||
@@ -13108,15 +13316,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
more = drawAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
- mPrivateFlags3 |= VIEW_IS_ANIMATING_TRANSFORM;
+ mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.mChildTransformation;
} else {
- if ((mPrivateFlags3 & VIEW_IS_ANIMATING_TRANSFORM) == VIEW_IS_ANIMATING_TRANSFORM &&
+ if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) == PFLAG3_VIEW_IS_ANIMATING_TRANSFORM &&
mDisplayList != null) {
// No longer animating: clear out old animation matrix
mDisplayList.setAnimationMatrix(null);
- mPrivateFlags3 &= ~VIEW_IS_ANIMATING_TRANSFORM;
+ mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
if (!useDisplayListProperties &&
(flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
@@ -13135,27 +13343,25 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// Sets the flag as early as possible to allow draw() implementations
// to call invalidate() successfully when doing animations
- mPrivateFlags |= DRAWN;
-
- if (!concatMatrix && canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) &&
- (mPrivateFlags & DRAW_ANIMATION) == 0) {
- mPrivateFlags2 |= VIEW_QUICK_REJECTED;
+ mPrivateFlags |= PFLAG_DRAWN;
+
+ if (!concatMatrix &&
+ (flags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS |
+ ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN &&
+ canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) &&
+ (mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) {
+ mPrivateFlags2 |= PFLAG2_VIEW_QUICK_REJECTED;
return more;
}
- mPrivateFlags2 &= ~VIEW_QUICK_REJECTED;
+ mPrivateFlags2 &= ~PFLAG2_VIEW_QUICK_REJECTED;
if (hardwareAccelerated) {
// Clear INVALIDATED flag to allow invalidation to occur during rendering, but
// retain the flag's value temporarily in the mRecreateDisplayList flag
- mRecreateDisplayList = (mPrivateFlags & INVALIDATED) == INVALIDATED;
- mPrivateFlags &= ~INVALIDATED;
+ mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) == PFLAG_INVALIDATED;
+ mPrivateFlags &= ~PFLAG_INVALIDATED;
}
- computeScroll();
-
- final int sx = mScrollX;
- final int sy = mScrollY;
-
DisplayList displayList = null;
Bitmap cache = null;
boolean hasDisplayList = false;
@@ -13202,6 +13408,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
}
+ int sx = 0;
+ int sy = 0;
+ if (!hasDisplayList) {
+ computeScroll();
+ sx = mScrollX;
+ sy = mScrollY;
+ }
+
final boolean hasNoCache = cache == null || hasDisplayList;
final boolean offsetForScroll = cache == null && !hasDisplayList &&
layerType != LAYER_TYPE_HARDWARE;
@@ -13229,7 +13443,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
float alpha = useDisplayListProperties ? 1 : getAlpha();
if (transformToApply != null || alpha < 1 || !hasIdentityMatrix() ||
- (mPrivateFlags3 & VIEW_IS_ANIMATING_ALPHA) == VIEW_IS_ANIMATING_ALPHA) {
+ (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) == PFLAG3_VIEW_IS_ANIMATING_ALPHA) {
if (transformToApply != null || !childHasIdentityMatrix) {
int transX = 0;
int transY = 0;
@@ -13269,11 +13483,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// Deal with alpha if it is or used to be <1
if (alpha < 1 ||
- (mPrivateFlags3 & VIEW_IS_ANIMATING_ALPHA) == VIEW_IS_ANIMATING_ALPHA) {
+ (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) == PFLAG3_VIEW_IS_ANIMATING_ALPHA) {
if (alpha < 1) {
- mPrivateFlags3 |= VIEW_IS_ANIMATING_ALPHA;
+ mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_ALPHA;
} else {
- mPrivateFlags3 &= ~VIEW_IS_ANIMATING_ALPHA;
+ mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_ALPHA;
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
if (hasNoCache) {
@@ -13294,13 +13508,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
} else {
// Alpha is handled by the child directly, clobber the layer's alpha
- mPrivateFlags |= ALPHA_SET;
+ mPrivateFlags |= PFLAG_ALPHA_SET;
}
}
}
- } else if ((mPrivateFlags & ALPHA_SET) == ALPHA_SET) {
+ } else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
onSetAlpha(255);
- mPrivateFlags &= ~ALPHA_SET;
+ mPrivateFlags &= ~PFLAG_ALPHA_SET;
}
if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) == ViewGroup.FLAG_CLIP_CHILDREN &&
@@ -13347,19 +13561,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (!layerRendered) {
if (!hasDisplayList) {
// Fast path for layouts with no backgrounds
- if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
- mPrivateFlags &= ~DIRTY_MASK;
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
} else {
- mPrivateFlags &= ~DIRTY_MASK;
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((HardwareCanvas) canvas).drawDisplayList(displayList, null, flags);
}
}
} else if (cache != null) {
- mPrivateFlags &= ~DIRTY_MASK;
+ mPrivateFlags &= ~PFLAG_DIRTY_MASK;
Paint cachePaint;
if (layerType == LAYER_TYPE_NONE) {
@@ -13399,7 +13613,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// display lists to render, force an invalidate to allow the animation to
// continue drawing another frame
parent.invalidate(true);
- if (a.hasAlpha() && (mPrivateFlags & ALPHA_SET) == ALPHA_SET) {
+ if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
// alpha animations should cause the child to recreate its display list
invalidate(true);
}
@@ -13421,9 +13635,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
- final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
+ final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
- mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
+ mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
@@ -13678,12 +13892,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
String output = "";
int numFlags = 0;
- if ((privateFlags & WANTS_FOCUS) == WANTS_FOCUS) {
+ if ((privateFlags & PFLAG_WANTS_FOCUS) == PFLAG_WANTS_FOCUS) {
output += "WANTS_FOCUS";
numFlags++;
}
- if ((privateFlags & FOCUSED) == FOCUSED) {
+ if ((privateFlags & PFLAG_FOCUSED) == PFLAG_FOCUSED) {
if (numFlags > 0) {
output += " ";
}
@@ -13691,7 +13905,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
numFlags++;
}
- if ((privateFlags & SELECTED) == SELECTED) {
+ if ((privateFlags & PFLAG_SELECTED) == PFLAG_SELECTED) {
if (numFlags > 0) {
output += " ";
}
@@ -13699,7 +13913,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
numFlags++;
}
- if ((privateFlags & IS_ROOT_NAMESPACE) == IS_ROOT_NAMESPACE) {
+ if ((privateFlags & PFLAG_IS_ROOT_NAMESPACE) == PFLAG_IS_ROOT_NAMESPACE) {
if (numFlags > 0) {
output += " ";
}
@@ -13707,7 +13921,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
numFlags++;
}
- if ((privateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+ if ((privateFlags & PFLAG_HAS_BOUNDS) == PFLAG_HAS_BOUNDS) {
if (numFlags > 0) {
output += " ";
}
@@ -13715,7 +13929,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
numFlags++;
}
- if ((privateFlags & DRAWN) == DRAWN) {
+ if ((privateFlags & PFLAG_DRAWN) == PFLAG_DRAWN) {
if (numFlags > 0) {
output += " ";
}
@@ -13732,7 +13946,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @return true if the layout will be forced during next layout pass
*/
public boolean isLayoutRequested() {
- return (mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT;
+ return (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
}
/**
@@ -13762,9 +13976,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int oldB = mBottom;
int oldR = mRight;
boolean changed = setFrame(l, t, r, b);
- if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
+ if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
- mPrivateFlags &= ~LAYOUT_REQUIRED;
+ mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
@@ -13776,7 +13990,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
}
}
- mPrivateFlags &= ~FORCE_LAYOUT;
+ mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
}
/**
@@ -13820,7 +14034,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
changed = true;
// Remember our drawn bit
- int drawn = mPrivateFlags & DRAWN;
+ int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
@@ -13839,11 +14053,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}
- mPrivateFlags |= HAS_BOUNDS;
+ mPrivateFlags |= PFLAG_HAS_BOUNDS;
if (sizeChanged) {
- if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
+ if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
// A change in dimension means an auto-centered pivot point changes, too
if (mTransformationInfo != null) {
mTransformationInfo.mMatrixDirty = true;
@@ -13858,7 +14072,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// This is because someone may have invalidated this view
// before this call to setFrame came in, thereby clearing
// the DRAWN bit.
- mPrivateFlags |= DRAWN;
+ mPrivateFlags |= PFLAG_DRAWN;
invalidate(sizeChanged);
// parent display list may need to be recreated based on a change in the bounds
// of any child
@@ -13963,13 +14177,42 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
- * Return the layout direction of a given Drawable.
- *
- * @param who the Drawable to query
- * @hide
- */
- public int getResolvedLayoutDirection(Drawable who) {
- return (who == mBackground) ? getResolvedLayoutDirection() : LAYOUT_DIRECTION_DEFAULT;
+ * Resolve the Drawables depending on the layout direction. This is implicitly supposing
+ * that the View directionality can and will be resolved before its Drawables.
+ *
+ * Will call {@link View#onResolveDrawables} when resolution is done.
+ *
+ * @hide
+ */
+ public void resolveDrawables() {
+ if (mBackground != null) {
+ mBackground.setLayoutDirection(getLayoutDirection());
+ }
+ mPrivateFlags2 |= PFLAG2_DRAWABLE_RESOLVED;
+ onResolveDrawables(getLayoutDirection());
+ }
+
+ /**
+ * Called when layout direction has been resolved.
+ *
+ * The default implementation does nothing.
+ *
+ * @param layoutDirection The resolved layout direction.
+ *
+ * @see #LAYOUT_DIRECTION_LTR
+ * @see #LAYOUT_DIRECTION_RTL
+ *
+ * @hide
+ */
+ public void onResolveDrawables(int layoutDirection) {
+ }
+
+ private void resetResolvedDrawables() {
+ mPrivateFlags2 &= ~PFLAG2_DRAWABLE_RESOLVED;
+ }
+
+ private boolean isDrawablesResolved() {
+ return (mPrivateFlags2 & PFLAG2_DRAWABLE_RESOLVED) == PFLAG2_DRAWABLE_RESOLVED;
}
/**
@@ -14020,7 +14263,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #getDrawableState
*/
public void refreshDrawableState() {
- mPrivateFlags |= DRAWABLE_STATE_DIRTY;
+ mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
drawableStateChanged();
ViewParent parent = mParent;
@@ -14040,11 +14283,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #onCreateDrawableState(int)
*/
public final int[] getDrawableState() {
- if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) {
+ if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {
return mDrawableState;
} else {
mDrawableState = onCreateDrawableState(0);
- mPrivateFlags &= ~DRAWABLE_STATE_DIRTY;
+ mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;
return mDrawableState;
}
}
@@ -14075,12 +14318,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int privateFlags = mPrivateFlags;
int viewStateIndex = 0;
- if ((privateFlags & PRESSED) != 0) viewStateIndex |= VIEW_STATE_PRESSED;
+ if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= VIEW_STATE_PRESSED;
if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= VIEW_STATE_ENABLED;
if (isFocused()) viewStateIndex |= VIEW_STATE_FOCUSED;
- if ((privateFlags & SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED;
+ if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED;
if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED;
- if ((privateFlags & ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED;
+ if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED;
if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&
HardwareRenderer.isAvailable()) {
// This is set if HW acceleration is requested, even if the current
@@ -14088,11 +14331,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// windows to better match their app.
viewStateIndex |= VIEW_STATE_ACCELERATED;
}
- if ((privateFlags & HOVERED) != 0) viewStateIndex |= VIEW_STATE_HOVERED;
+ if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_HOVERED;
final int privateFlags2 = mPrivateFlags2;
- if ((privateFlags2 & DRAG_CAN_ACCEPT) != 0) viewStateIndex |= VIEW_STATE_DRAG_CAN_ACCEPT;
- if ((privateFlags2 & DRAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_DRAG_HOVERED;
+ if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) viewStateIndex |= VIEW_STATE_DRAG_CAN_ACCEPT;
+ if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_DRAG_HOVERED;
drawableState = VIEW_STATE_SETS[viewStateIndex];
@@ -14100,10 +14343,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (false) {
Log.i("View", "drawableStateIndex=" + viewStateIndex);
Log.i("View", toString()
- + " pressed=" + ((privateFlags & PRESSED) != 0)
+ + " pressed=" + ((privateFlags & PFLAG_PRESSED) != 0)
+ " en=" + ((mViewFlags & ENABLED_MASK) == ENABLED)
+ " fo=" + hasFocus()
- + " sl=" + ((privateFlags & SELECTED) != 0)
+ + " sl=" + ((privateFlags & PFLAG_SELECTED) != 0)
+ " wf=" + hasWindowFocus()
+ ": " + Arrays.toString(drawableState));
}
@@ -14167,7 +14410,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
@RemotableViewMethod
public void setBackgroundColor(int color) {
if (mBackground instanceof ColorDrawable) {
- ((ColorDrawable) mBackground).setColor(color);
+ ((ColorDrawable) mBackground.mutate()).setColor(color);
+ computeOpaqueFlags();
} else {
setBackground(new ColorDrawable(color));
}
@@ -14215,6 +14459,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
@Deprecated
public void setBackgroundDrawable(Drawable background) {
+ computeOpaqueFlags();
+
if (background == mBackground) {
return;
}
@@ -14238,14 +14484,21 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
padding = new Rect();
sThreadLocal.set(padding);
}
+ resetResolvedDrawables();
+ background.setLayoutDirection(getLayoutDirection());
if (background.getPadding(padding)) {
- switch (background.getResolvedLayoutDirectionSelf()) {
+ resetResolvedPadding();
+ switch (background.getLayoutDirection()) {
case LAYOUT_DIRECTION_RTL:
- setPadding(padding.right, padding.top, padding.left, padding.bottom);
+ mUserPaddingLeftInitial = padding.right;
+ mUserPaddingRightInitial = padding.left;
+ internalSetPadding(padding.right, padding.top, padding.left, padding.bottom);
break;
case LAYOUT_DIRECTION_LTR:
default:
- setPadding(padding.left, padding.top, padding.right, padding.bottom);
+ mUserPaddingLeftInitial = padding.left;
+ mUserPaddingRightInitial = padding.right;
+ internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);
}
}
@@ -14263,23 +14516,23 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
background.setVisible(getVisibility() == VISIBLE, false);
mBackground = background;
- if ((mPrivateFlags & SKIP_DRAW) != 0) {
- mPrivateFlags &= ~SKIP_DRAW;
- mPrivateFlags |= ONLY_DRAWS_BACKGROUND;
+ if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
+ mPrivateFlags &= ~PFLAG_SKIP_DRAW;
+ mPrivateFlags |= PFLAG_ONLY_DRAWS_BACKGROUND;
requestLayout = true;
}
} else {
/* Remove the background */
mBackground = null;
- if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) {
+ if ((mPrivateFlags & PFLAG_ONLY_DRAWS_BACKGROUND) != 0) {
/*
* This view ONLY drew the background before and we're removing
* the background, so now it won't draw anything
* (hence we SKIP_DRAW)
*/
- mPrivateFlags &= ~ONLY_DRAWS_BACKGROUND;
- mPrivateFlags |= SKIP_DRAW;
+ mPrivateFlags &= ~PFLAG_ONLY_DRAWS_BACKGROUND;
+ mPrivateFlags |= PFLAG_SKIP_DRAW;
}
/*
@@ -14335,14 +14588,21 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param bottom the bottom padding in pixels
*/
public void setPadding(int left, int top, int right, int bottom) {
- mUserPaddingStart = -1;
- mUserPaddingEnd = -1;
- mUserPaddingRelative = false;
+ resetResolvedPadding();
+
+ mUserPaddingStart = UNDEFINED_PADDING;
+ mUserPaddingEnd = UNDEFINED_PADDING;
+
+ mUserPaddingLeftInitial = left;
+ mUserPaddingRightInitial = right;
internalSetPadding(left, top, right, bottom);
}
- private void internalSetPadding(int left, int top, int right, int bottom) {
+ /**
+ * @hide
+ */
+ protected void internalSetPadding(int left, int top, int right, int bottom) {
mUserPaddingLeft = left;
mUserPaddingRight = right;
mUserPaddingBottom = bottom;
@@ -14357,7 +14617,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
? 0 : getVerticalScrollbarWidth();
switch (mVerticalScrollbarPosition) {
case SCROLLBAR_POSITION_DEFAULT:
- if (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ if (isLayoutRtl()) {
left += offset;
} else {
right += offset;
@@ -14402,25 +14662,36 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
/**
* Sets the relative padding. The view may add on the space required to display
* the scrollbars, depending on the style and visibility of the scrollbars.
+ * So the values returned from {@link #getPaddingStart}, {@link #getPaddingTop},
+ * {@link #getPaddingEnd} and {@link #getPaddingBottom} may be different
* from the values set in this call.
*
+ * @attr ref android.R.styleable#View_padding
+ * @attr ref android.R.styleable#View_paddingBottom
+ * @attr ref android.R.styleable#View_paddingStart
+ * @attr ref android.R.styleable#View_paddingEnd
+ * @attr ref android.R.styleable#View_paddingTop
* @param start the start padding in pixels
* @param top the top padding in pixels
* @param end the end padding in pixels
* @param bottom the bottom padding in pixels
- * @hide
*/
public void setPaddingRelative(int start, int top, int end, int bottom) {
+ resetResolvedPadding();
+
mUserPaddingStart = start;
mUserPaddingEnd = end;
- mUserPaddingRelative = true;
- switch(getResolvedLayoutDirection()) {
+ switch(getLayoutDirection()) {
case LAYOUT_DIRECTION_RTL:
+ mUserPaddingLeftInitial = end;
+ mUserPaddingRightInitial = start;
internalSetPadding(end, top, start, bottom);
break;
case LAYOUT_DIRECTION_LTR:
default:
+ mUserPaddingLeftInitial = start;
+ mUserPaddingRightInitial = end;
internalSetPadding(start, top, end, bottom);
}
}
@@ -14453,6 +14724,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @return the left padding in pixels
*/
public int getPaddingLeft() {
+ if (!isPaddingResolved()) {
+ resolvePadding();
+ }
return mPaddingLeft;
}
@@ -14462,10 +14736,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* required to display the scrollbars as well.
*
* @return the start padding in pixels
- * @hide
*/
public int getPaddingStart() {
- return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+ if (!isPaddingResolved()) {
+ resolvePadding();
+ }
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
mPaddingRight : mPaddingLeft;
}
@@ -14477,6 +14753,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @return the right padding in pixels
*/
public int getPaddingRight() {
+ if (!isPaddingResolved()) {
+ resolvePadding();
+ }
return mPaddingRight;
}
@@ -14486,22 +14765,43 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* required to display the scrollbars as well.
*
* @return the end padding in pixels
- * @hide
*/
public int getPaddingEnd() {
- return (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+ if (!isPaddingResolved()) {
+ resolvePadding();
+ }
+ return (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
mPaddingLeft : mPaddingRight;
}
/**
* Return if the padding as been set thru relative values
- * {@link #setPaddingRelative(int, int, int, int)}
+ * {@link #setPaddingRelative(int, int, int, int)} or thru
+ * @attr ref android.R.styleable#View_paddingStart or
+ * @attr ref android.R.styleable#View_paddingEnd
*
* @return true if the padding is relative or false if it is not.
- * @hide
*/
public boolean isPaddingRelative() {
- return mUserPaddingRelative;
+ return (mUserPaddingStart != UNDEFINED_PADDING || mUserPaddingEnd != UNDEFINED_PADDING);
+ }
+
+ /**
+ * @hide
+ */
+ public void resetPaddingToInitialValues() {
+ if (isRtlCompatibilityMode()) {
+ mPaddingLeft = mUserPaddingLeftInitial;
+ mPaddingRight = mUserPaddingRightInitial;
+ } else {
+ if (isLayoutRtl()) {
+ mPaddingLeft = mUserPaddingRightInitial;
+ mPaddingRight = mUserPaddingLeftInitial;
+ } else {
+ mPaddingLeft = mUserPaddingLeftInitial;
+ mPaddingRight = mUserPaddingRightInitial;
+ }
+ }
}
/**
@@ -14530,8 +14830,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param selected true if the view must be selected, false otherwise
*/
public void setSelected(boolean selected) {
- if (((mPrivateFlags & SELECTED) != 0) != selected) {
- mPrivateFlags = (mPrivateFlags & ~SELECTED) | (selected ? SELECTED : 0);
+ if (((mPrivateFlags & PFLAG_SELECTED) != 0) != selected) {
+ mPrivateFlags = (mPrivateFlags & ~PFLAG_SELECTED) | (selected ? PFLAG_SELECTED : 0);
if (!selected) resetPressedState();
invalidate(true);
refreshDrawableState();
@@ -14559,7 +14859,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
@ViewDebug.ExportedProperty
public boolean isSelected() {
- return (mPrivateFlags & SELECTED) != 0;
+ return (mPrivateFlags & PFLAG_SELECTED) != 0;
}
/**
@@ -14576,8 +14876,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param activated true if the view must be activated, false otherwise
*/
public void setActivated(boolean activated) {
- if (((mPrivateFlags & ACTIVATED) != 0) != activated) {
- mPrivateFlags = (mPrivateFlags & ~ACTIVATED) | (activated ? ACTIVATED : 0);
+ if (((mPrivateFlags & PFLAG_ACTIVATED) != 0) != activated) {
+ mPrivateFlags = (mPrivateFlags & ~PFLAG_ACTIVATED) | (activated ? PFLAG_ACTIVATED : 0);
invalidate(true);
refreshDrawableState();
dispatchSetActivated(activated);
@@ -14601,7 +14901,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
@ViewDebug.ExportedProperty
public boolean isActivated() {
- return (mPrivateFlags & ACTIVATED) != 0;
+ return (mPrivateFlags & PFLAG_ACTIVATED) != 0;
}
/**
@@ -14879,6 +15179,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public void setId(int id) {
mID = id;
+ if (mID == View.NO_ID && mLabelForId != View.NO_ID) {
+ mID = generateViewId();
+ }
}
/**
@@ -14889,9 +15192,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public void setIsRootNamespace(boolean isRoot) {
if (isRoot) {
- mPrivateFlags |= IS_ROOT_NAMESPACE;
+ mPrivateFlags |= PFLAG_IS_ROOT_NAMESPACE;
} else {
- mPrivateFlags &= ~IS_ROOT_NAMESPACE;
+ mPrivateFlags &= ~PFLAG_IS_ROOT_NAMESPACE;
}
}
@@ -14901,7 +15204,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @return true if the view belongs to the root namespace, false otherwise
*/
public boolean isRootNamespace() {
- return (mPrivateFlags&IS_ROOT_NAMESPACE) != 0;
+ return (mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE) != 0;
}
/**
@@ -15050,7 +15353,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
Log.d(VIEW_LOG_TAG, output);
- if ((mPrivateFlags & FOCUSED) != 0) {
+ if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
output = debugIndent(depth) + " FOCUSED";
Log.d(VIEW_LOG_TAG, output);
}
@@ -15130,12 +15433,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* tree.
*/
public void requestLayout() {
- mPrivateFlags |= FORCE_LAYOUT;
- mPrivateFlags |= INVALIDATED;
-
- if (mLayoutParams != null) {
- mLayoutParams.onResolveLayoutDirection(getResolvedLayoutDirection());
- }
+ mPrivateFlags |= PFLAG_FORCE_LAYOUT;
+ mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
@@ -15148,8 +15447,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* on the parent.
*/
public void forceLayout() {
- mPrivateFlags |= FORCE_LAYOUT;
- mPrivateFlags |= INVALIDATED;
+ mPrivateFlags |= PFLAG_FORCE_LAYOUT;
+ mPrivateFlags |= PFLAG_INVALIDATED;
}
/**
@@ -15173,25 +15472,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
- if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
+ if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
- mPrivateFlags &= ~MEASURED_DIMENSION_SET;
+ mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
+
+ resolveRtlPropertiesIfNeeded();
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
- if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
+ if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
- mPrivateFlags |= LAYOUT_REQUIRED;
+ mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
@@ -15265,7 +15566,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
- mPrivateFlags |= MEASURED_DIMENSION_SET;
+ mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
/**
@@ -15509,7 +15810,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #getAnimation()
*/
protected void onAnimationStart() {
- mPrivateFlags |= ANIMATION_STARTED;
+ mPrivateFlags |= PFLAG_ANIMATION_STARTED;
}
/**
@@ -15521,7 +15822,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #getAnimation()
*/
protected void onAnimationEnd() {
- mPrivateFlags &= ~ANIMATION_STARTED;
+ mPrivateFlags &= ~PFLAG_ANIMATION_STARTED;
}
/**
@@ -15558,14 +15859,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
final AttachInfo attachInfo = mAttachInfo;
if (region != null && attachInfo != null) {
final int pflags = mPrivateFlags;
- if ((pflags & SKIP_DRAW) == 0) {
+ if ((pflags & PFLAG_SKIP_DRAW) == 0) {
// The SKIP_DRAW flag IS NOT set, so this view draws. We need to
// remove it from the transparent region.
final int[] location = attachInfo.mTransparentLocation;
getLocationInWindow(location);
region.op(location[0], location[1], location[0] + mRight - mLeft,
location[1] + mBottom - mTop, Region.Op.DIFFERENCE);
- } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBackground != null) {
+ } else if ((pflags & PFLAG_ONLY_DRAWS_BACKGROUND) != 0 && mBackground != null) {
// The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable
// exists, so we remove the background drawable's non-transparent
// parts from this transparent region.
@@ -16042,7 +16343,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
boolean canAcceptDrag() {
- return (mPrivateFlags2 & DRAG_CAN_ACCEPT) != 0;
+ return (mPrivateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0;
}
/**
@@ -16297,6 +16598,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE}
+ *
* @hide
*/
@ViewDebug.ExportedProperty(category = "text", mapping = {
@@ -16307,8 +16609,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
@ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"),
@ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE")
})
- public int getTextDirection() {
- return (mPrivateFlags2 & TEXT_DIRECTION_MASK) >> TEXT_DIRECTION_MASK_SHIFT;
+ public int getRawTextDirection() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_MASK) >> PFLAG2_TEXT_DIRECTION_MASK_SHIFT;
}
/**
@@ -16322,15 +16624,22 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE}
- * @hide
+ *
+ * Resolution will be done if the value is set to TEXT_DIRECTION_INHERIT. The resolution
+ * proceeds up the parent chain of the view to get the value. If there is no parent, then it will
+ * return the default {@link #TEXT_DIRECTION_FIRST_STRONG}.
*/
public void setTextDirection(int textDirection) {
- if (getTextDirection() != textDirection) {
+ if (getRawTextDirection() != textDirection) {
// Reset the current text direction and the resolved one
- mPrivateFlags2 &= ~TEXT_DIRECTION_MASK;
+ mPrivateFlags2 &= ~PFLAG2_TEXT_DIRECTION_MASK;
resetResolvedTextDirection();
// Set the new text direction
- mPrivateFlags2 |= ((textDirection << TEXT_DIRECTION_MASK_SHIFT) & TEXT_DIRECTION_MASK);
+ mPrivateFlags2 |= ((textDirection << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) & PFLAG2_TEXT_DIRECTION_MASK);
+ // Do resolution
+ resolveTextDirection();
+ // Notify change
+ onRtlPropertiesChanged(getLayoutDirection());
// Refresh
requestLayout();
invalidate(true);
@@ -16340,11 +16649,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
/**
* Return the resolved text direction.
*
- * This needs resolution if the value is TEXT_DIRECTION_INHERIT. The resolution matches
- * {@link #getTextDirection()}if it is not TEXT_DIRECTION_INHERIT, otherwise resolution proceeds
- * up the parent chain of the view. if there is no parent, then it will return the default
- * {@link #TEXT_DIRECTION_FIRST_STRONG}.
- *
* @return the resolved text direction. Returns one of:
*
* {@link #TEXT_DIRECTION_FIRST_STRONG}
@@ -16352,51 +16656,56 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@link #TEXT_DIRECTION_LTR},
* {@link #TEXT_DIRECTION_RTL},
* {@link #TEXT_DIRECTION_LOCALE}
- * @hide
*/
- public int getResolvedTextDirection() {
- // The text direction will be resolved only if needed
- if ((mPrivateFlags2 & TEXT_DIRECTION_RESOLVED) != TEXT_DIRECTION_RESOLVED) {
- resolveTextDirection();
- }
- return (mPrivateFlags2 & TEXT_DIRECTION_RESOLVED_MASK) >> TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
+ public int getTextDirection() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED_MASK) >> PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
}
/**
- * Resolve the text direction. Will call {@link View#onResolvedTextDirectionChanged} when
- * resolution is done.
+ * Resolve the text direction.
+ *
+ * @return true if resolution has been done, false otherwise.
+ *
* @hide
*/
- public void resolveTextDirection() {
+ public boolean resolveTextDirection() {
// Reset any previous text direction resolution
- mPrivateFlags2 &= ~(TEXT_DIRECTION_RESOLVED | TEXT_DIRECTION_RESOLVED_MASK);
+ mPrivateFlags2 &= ~(PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_DIRECTION_RESOLVED_MASK);
if (hasRtlSupport()) {
// Set resolved text direction flag depending on text direction flag
- final int textDirection = getTextDirection();
+ final int textDirection = getRawTextDirection();
switch(textDirection) {
case TEXT_DIRECTION_INHERIT:
- if (canResolveTextDirection()) {
- ViewGroup viewGroup = ((ViewGroup) mParent);
-
- // Set current resolved direction to the same value as the parent's one
- final int parentResolvedDirection = viewGroup.getResolvedTextDirection();
- switch (parentResolvedDirection) {
- case TEXT_DIRECTION_FIRST_STRONG:
- case TEXT_DIRECTION_ANY_RTL:
- case TEXT_DIRECTION_LTR:
- case TEXT_DIRECTION_RTL:
- case TEXT_DIRECTION_LOCALE:
- mPrivateFlags2 |=
- (parentResolvedDirection << TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
- break;
- default:
- // Default resolved direction is "first strong" heuristic
- mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT;
- }
- } else {
+ if (!canResolveTextDirection()) {
// We cannot do the resolution if there is no parent, so use the default one
- mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT;
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+ // Resolution will need to happen again later
+ return false;
+ }
+
+ View parent = ((View) mParent);
+ // Parent has not yet resolved, so we still return the default
+ if (!parent.isTextDirectionResolved()) {
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+ // Resolution will need to happen again later
+ return false;
+ }
+
+ // Set current resolved direction to the same value as the parent's one
+ final int parentResolvedDirection = parent.getTextDirection();
+ switch (parentResolvedDirection) {
+ case TEXT_DIRECTION_FIRST_STRONG:
+ case TEXT_DIRECTION_ANY_RTL:
+ case TEXT_DIRECTION_LTR:
+ case TEXT_DIRECTION_RTL:
+ case TEXT_DIRECTION_LOCALE:
+ mPrivateFlags2 |=
+ (parentResolvedDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
+ break;
+ default:
+ // Default resolved direction is "first strong" heuristic
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
}
break;
case TEXT_DIRECTION_FIRST_STRONG:
@@ -16405,65 +16714,64 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
case TEXT_DIRECTION_RTL:
case TEXT_DIRECTION_LOCALE:
// Resolved direction is the same as text direction
- mPrivateFlags2 |= (textDirection << TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
+ mPrivateFlags2 |= (textDirection << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT);
break;
default:
// Default resolved direction is "first strong" heuristic
- mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT;
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
}
} else {
// Default resolved direction is "first strong" heuristic
- mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT;
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
}
// Set to resolved
- mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED;
- onResolvedTextDirectionChanged();
- }
-
- /**
- * Called when text direction has been resolved. Subclasses that care about text direction
- * resolution should override this method.
- *
- * The default implementation does nothing.
- * @hide
- */
- public void onResolvedTextDirectionChanged() {
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED;
+ return true;
}
/**
* 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 (getTextDirection()) {
+ private boolean canResolveTextDirection() {
+ switch (getRawTextDirection()) {
case TEXT_DIRECTION_INHERIT:
- return (mParent != null) && (mParent instanceof ViewGroup);
+ return (mParent != null) && (mParent instanceof View) &&
+ ((View) mParent).canResolveTextDirection();
default:
return true;
}
}
/**
- * Reset resolved text direction. Text direction can be resolved with a call to
- * getResolvedTextDirection(). Will call {@link View#onResolvedTextDirectionReset} when
- * reset is done.
+ * Reset resolved text direction. Text direction will be resolved during a call to
+ * {@link #onMeasure(int, int)}.
+ *
* @hide
*/
public void resetResolvedTextDirection() {
- mPrivateFlags2 &= ~(TEXT_DIRECTION_RESOLVED | TEXT_DIRECTION_RESOLVED_MASK);
- onResolvedTextDirectionReset();
+ // Reset any previous text direction resolution
+ mPrivateFlags2 &= ~(PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_DIRECTION_RESOLVED_MASK);
+ // Set to default value
+ mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
}
/**
- * Called when text direction is reset. Subclasses that care about text direction reset should
- * override this method and do a reset of the text direction of their children. The default
- * implementation does nothing.
+ * @return true if text direction is inherited.
+ *
* @hide
*/
- public void onResolvedTextDirectionReset() {
+ public boolean isTextDirectionInherited() {
+ return (getRawTextDirection() == TEXT_DIRECTION_INHERIT);
+ }
+
+ /**
+ * @return true if text direction is resolved.
+ */
+ private boolean isTextDirectionResolved() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED) == PFLAG2_TEXT_DIRECTION_RESOLVED;
}
/**
@@ -16479,6 +16787,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@link #TEXT_ALIGNMENT_TEXT_END},
* {@link #TEXT_ALIGNMENT_VIEW_START},
* {@link #TEXT_ALIGNMENT_VIEW_END}
+ *
* @hide
*/
@ViewDebug.ExportedProperty(category = "text", mapping = {
@@ -16490,8 +16799,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
@ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"),
@ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END")
})
- public int getTextAlignment() {
- return (mPrivateFlags2 & TEXT_ALIGNMENT_MASK) >> TEXT_ALIGNMENT_MASK_SHIFT;
+ public int getRawTextAlignment() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_MASK) >> PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT;
}
/**
@@ -16507,16 +16816,24 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@link #TEXT_ALIGNMENT_VIEW_START},
* {@link #TEXT_ALIGNMENT_VIEW_END}
*
+ * Resolution will be done if the value is set to TEXT_ALIGNMENT_INHERIT. The resolution
+ * proceeds up the parent chain of the view to get the value. If there is no parent, then it
+ * will return the default {@link #TEXT_ALIGNMENT_GRAVITY}.
+ *
* @attr ref android.R.styleable#View_textAlignment
- * @hide
*/
public void setTextAlignment(int textAlignment) {
- if (textAlignment != getTextAlignment()) {
+ if (textAlignment != getRawTextAlignment()) {
// Reset the current and resolved text alignment
- mPrivateFlags2 &= ~TEXT_ALIGNMENT_MASK;
+ mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK;
resetResolvedTextAlignment();
// Set the new text alignment
- mPrivateFlags2 |= ((textAlignment << TEXT_ALIGNMENT_MASK_SHIFT) & TEXT_ALIGNMENT_MASK);
+ mPrivateFlags2 |=
+ ((textAlignment << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) & PFLAG2_TEXT_ALIGNMENT_MASK);
+ // Do resolution
+ resolveTextAlignment();
+ // Notify change
+ onRtlPropertiesChanged(getLayoutDirection());
// Refresh
requestLayout();
invalidate(true);
@@ -16526,10 +16843,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
/**
* Return the resolved text alignment.
*
- * The resolved text alignment. This needs resolution if the value is
- * TEXT_ALIGNMENT_INHERIT. The resolution matches {@link #setTextAlignment(int)} if it is
- * not TEXT_ALIGNMENT_INHERIT, otherwise resolution proceeds up the parent chain of the view.
- *
* @return the resolved text alignment. Returns one of:
*
* {@link #TEXT_ALIGNMENT_GRAVITY},
@@ -16538,7 +16851,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@link #TEXT_ALIGNMENT_TEXT_END},
* {@link #TEXT_ALIGNMENT_VIEW_START},
* {@link #TEXT_ALIGNMENT_VIEW_END}
- * @hide
*/
@ViewDebug.ExportedProperty(category = "text", mapping = {
@ViewDebug.IntToString(from = TEXT_ALIGNMENT_INHERIT, to = "INHERIT"),
@@ -16549,53 +16861,59 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
@ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"),
@ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END")
})
- public int getResolvedTextAlignment() {
- // If text alignment is not resolved, then resolve it
- if ((mPrivateFlags2 & TEXT_ALIGNMENT_RESOLVED) != TEXT_ALIGNMENT_RESOLVED) {
- resolveTextAlignment();
- }
- return (mPrivateFlags2 & TEXT_ALIGNMENT_RESOLVED_MASK) >> TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+ public int getTextAlignment() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK) >>
+ PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
}
/**
- * Resolve the text alignment. Will call {@link View#onResolvedTextAlignmentChanged} when
- * resolution is done.
+ * Resolve the text alignment.
+ *
+ * @return true if resolution has been done, false otherwise.
+ *
* @hide
*/
- public void resolveTextAlignment() {
+ public boolean resolveTextAlignment() {
// Reset any previous text alignment resolution
- mPrivateFlags2 &= ~(TEXT_ALIGNMENT_RESOLVED | TEXT_ALIGNMENT_RESOLVED_MASK);
+ mPrivateFlags2 &= ~(PFLAG2_TEXT_ALIGNMENT_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK);
if (hasRtlSupport()) {
// Set resolved text alignment flag depending on text alignment flag
- final int textAlignment = getTextAlignment();
+ final int textAlignment = getRawTextAlignment();
switch (textAlignment) {
case TEXT_ALIGNMENT_INHERIT:
// Check if we can resolve the text alignment
- if (canResolveLayoutDirection() && mParent instanceof View) {
- View view = (View) mParent;
-
- final int parentResolvedTextAlignment = view.getResolvedTextAlignment();
- switch (parentResolvedTextAlignment) {
- case TEXT_ALIGNMENT_GRAVITY:
- case TEXT_ALIGNMENT_TEXT_START:
- case TEXT_ALIGNMENT_TEXT_END:
- case TEXT_ALIGNMENT_CENTER:
- case TEXT_ALIGNMENT_VIEW_START:
- case TEXT_ALIGNMENT_VIEW_END:
- // Resolved text alignment is the same as the parent resolved
- // text alignment
- mPrivateFlags2 |=
- (parentResolvedTextAlignment << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT);
- break;
- default:
- // Use default resolved text alignment
- mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT;
- }
- }
- else {
+ if (!canResolveTextAlignment()) {
// We cannot do the resolution if there is no parent so use the default
- mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ // Resolution will need to happen again later
+ return false;
+ }
+ View parent = (View) mParent;
+
+ // Parent has not yet resolved, so we still return the default
+ if (!parent.isTextAlignmentResolved()) {
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ // Resolution will need to happen again later
+ return false;
+ }
+
+ final int parentResolvedTextAlignment = parent.getTextAlignment();
+ switch (parentResolvedTextAlignment) {
+ case TEXT_ALIGNMENT_GRAVITY:
+ case TEXT_ALIGNMENT_TEXT_START:
+ case TEXT_ALIGNMENT_TEXT_END:
+ case TEXT_ALIGNMENT_CENTER:
+ case TEXT_ALIGNMENT_VIEW_START:
+ case TEXT_ALIGNMENT_VIEW_END:
+ // Resolved text alignment is the same as the parent resolved
+ // text alignment
+ mPrivateFlags2 |=
+ (parentResolvedTextAlignment << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT);
+ break;
+ default:
+ // Use default resolved text alignment
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
}
break;
case TEXT_ALIGNMENT_GRAVITY:
@@ -16605,66 +16923,82 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
case TEXT_ALIGNMENT_VIEW_START:
case TEXT_ALIGNMENT_VIEW_END:
// Resolved text alignment is the same as text alignment
- mPrivateFlags2 |= (textAlignment << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT);
+ mPrivateFlags2 |= (textAlignment << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT);
break;
default:
// Use default resolved text alignment
- mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
}
} else {
// Use default resolved text alignment
- mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
}
// Set the resolved
- mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED;
- onResolvedTextAlignmentChanged();
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED;
+ return true;
}
/**
* 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 (getTextAlignment()) {
+ private boolean canResolveTextAlignment() {
+ switch (getRawTextAlignment()) {
case TEXT_DIRECTION_INHERIT:
- return (mParent != null);
+ return (mParent != null) && (mParent instanceof View) &&
+ ((View) mParent).canResolveTextAlignment();
default:
return true;
}
}
/**
- * Called when text alignment has been resolved. Subclasses that care about text alignment
- * resolution should override this method.
+ * Reset resolved text alignment. Text alignment will be resolved during a call to
+ * {@link #onMeasure(int, int)}.
*
- * The default implementation does nothing.
* @hide
*/
- public void onResolvedTextAlignmentChanged() {
+ public void resetResolvedTextAlignment() {
+ // Reset any previous text alignment resolution
+ mPrivateFlags2 &= ~(PFLAG2_TEXT_ALIGNMENT_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK);
+ // Set to default
+ mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
}
/**
- * Reset resolved text alignment. Text alignment can be resolved with a call to
- * getResolvedTextAlignment(). Will call {@link View#onResolvedTextAlignmentReset} when
- * reset is done.
+ * @return true if text alignment is inherited.
+ *
* @hide
*/
- public void resetResolvedTextAlignment() {
- // Reset any previous text alignment resolution
- mPrivateFlags2 &= ~(TEXT_ALIGNMENT_RESOLVED | TEXT_ALIGNMENT_RESOLVED_MASK);
- onResolvedTextAlignmentReset();
+ public boolean isTextAlignmentInherited() {
+ return (getRawTextAlignment() == TEXT_ALIGNMENT_INHERIT);
}
/**
- * Called when text alignment is reset. Subclasses that care about text alignment reset should
- * override this method and do a reset of the text alignment of their children. The default
- * implementation does nothing.
- * @hide
+ * @return true if text alignment is resolved.
*/
- public void onResolvedTextAlignmentReset() {
+ private boolean isTextAlignmentResolved() {
+ return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED) == PFLAG2_TEXT_ALIGNMENT_RESOLVED;
+ }
+
+ /**
+ * Generate a value suitable for use in {@link #setId(int)}.
+ * This value will not collide with ID values generated at build time by aapt for R.id.
+ *
+ * @return a generated ID value
+ */
+ public static int generateViewId() {
+ for (;;) {
+ final int result = sNextGeneratedId.get();
+ // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
+ int newValue = result + 1;
+ if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
+ if (sNextGeneratedId.compareAndSet(result, newValue)) {
+ return result;
+ }
+ }
}
//
@@ -16967,7 +17301,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
private final class CheckForTap implements Runnable {
public void run() {
- mPrivateFlags &= ~PREPRESSED;
+ mPrivateFlags &= ~PFLAG_PREPRESSED;
setPressed(true);
checkForLongClick(ViewConfiguration.getTapTimeout());
}
@@ -17315,6 +17649,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
final IBinder mWindowToken;
+ final Display mDisplay;
+
final Callbacks mRootCallbacks;
HardwareCanvas mHardwareCanvas;
@@ -17359,20 +17695,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int mWindowTop;
/**
- * Left actual position of this view's window.
- *
- * TODO: This is a workaround for 6623031. Remove when fixed.
- */
- int mActualWindowLeft;
-
- /**
- * Actual top position of this view's window.
- *
- * TODO: This is a workaround for 6623031. Remove when fixed.
- */
- int mActualWindowTop;
-
- /**
* Indicates whether views need to use 32-bit drawing caches
*/
boolean mUse32BitDrawingCache;
@@ -17542,6 +17864,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
final RectF mTmpTransformRect = new RectF();
/**
+ * Temporary for use in transforming invalidation rect
+ */
+ final Matrix mTmpMatrix = new Matrix();
+
+ /**
+ * Temporary for use in transforming invalidation rect
+ */
+ final Transformation mTmpTransformation = new Transformation();
+
+ /**
* Temporary list for use in collecting focusable descendents of a view.
*/
final ArrayList<View> mTempArrayList = new ArrayList<View>(24);
@@ -17578,11 +17910,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @param handler the events handler the view must use
*/
- AttachInfo(IWindowSession session, IWindow window,
+ AttachInfo(IWindowSession session, IWindow window, Display display,
ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
mSession = session;
mWindow = window;
mWindowToken = window.asBinder();
+ mDisplay = display;
mViewRootImpl = viewRootImpl;
mHandler = handler;
mRootCallbacks = effectPlayer;
@@ -17656,23 +17989,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// use use a height of 1, and then wack the matrix each time we
// actually use it.
shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
-
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+
this.host = host;
}
public void setFadeColor(int color) {
- if (color != 0 && color != mLastColor) {
+ if (color != mLastColor) {
mLastColor = color;
- color |= 0xFF000000;
-
- shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000,
- color & 0x00FFFFFF, Shader.TileMode.CLAMP);
- paint.setShader(shader);
- // Restore the default transfer mode (src_over)
- paint.setXfermode(null);
+ if (color != 0) {
+ shader = new LinearGradient(0, 0, 0, 1, color | 0xFF000000,
+ color & 0x00FFFFFF, Shader.TileMode.CLAMP);
+ paint.setShader(shader);
+ // Restore the default transfer mode (src_over)
+ paint.setXfermode(null);
+ } else {
+ shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
+ paint.setShader(shader);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
+ }
}
}
@@ -17960,4 +18297,64 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
return null;
}
}
+
+ private class MatchIdPredicate implements Predicate<View> {
+ public int mId;
+
+ @Override
+ public boolean apply(View view) {
+ return (view.mID == mId);
+ }
+ }
+
+ private class MatchLabelForPredicate implements Predicate<View> {
+ private int mLabeledId;
+
+ @Override
+ public boolean apply(View view) {
+ return (view.mLabelForId == mLabeledId);
+ }
+ }
+
+ /**
+ * Dump all private flags in readable format, useful for documentation and
+ * sanity checking.
+ */
+ private static void dumpFlags() {
+ final HashMap<String, String> found = Maps.newHashMap();
+ try {
+ for (Field field : View.class.getDeclaredFields()) {
+ final int modifiers = field.getModifiers();
+ if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) {
+ if (field.getType().equals(int.class)) {
+ final int value = field.getInt(null);
+ dumpFlag(found, field.getName(), value);
+ } else if (field.getType().equals(int[].class)) {
+ final int[] values = (int[]) field.get(null);
+ for (int i = 0; i < values.length; i++) {
+ dumpFlag(found, field.getName() + "[" + i + "]", values[i]);
+ }
+ }
+ }
+ }
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+
+ final ArrayList<String> keys = Lists.newArrayList();
+ keys.addAll(found.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ Log.d(VIEW_LOG_TAG, found.get(key));
+ }
+ }
+
+ private static void dumpFlag(HashMap<String, String> found, String name, int value) {
+ // Sort flags by prefix, then by bits, always keeping unique keys
+ final String bits = String.format("%32s", Integer.toBinaryString(value)).replace('0', ' ');
+ final int prefix = name.indexOf('_');
+ final String key = (prefix > 0 ? name.substring(0, prefix) : name) + bits + name;
+ final String output = bits + " " + name;
+ found.put(key, output);
+ }
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 823befb..499075e 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -20,6 +20,7 @@ import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Point;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.DisplayMetrics;
@@ -30,24 +31,6 @@ import android.util.SparseArray;
*/
public class ViewConfiguration {
/**
- * Expected bit depth of the display panel.
- *
- * @hide
- */
- public static final float PANEL_BIT_DEPTH = 24;
-
- /**
- * Minimum alpha required for a view to draw.
- *
- * @hide
- */
- public static final float ALPHA_THRESHOLD = 0.5f / PANEL_BIT_DEPTH;
- /**
- * @hide
- */
- public static final float ALPHA_THRESHOLD_INT = 0x7f / PANEL_BIT_DEPTH;
-
- /**
* Defines the width of the horizontal scrollbar and the height of the vertical scrollbar in
* dips
*/
@@ -295,15 +278,18 @@ public class ViewConfiguration {
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
- final Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
// Size of the screen in bytes, in ARGB_8888 format
- mMaximumDrawingCacheSize = 4 * display.getRawWidth() * display.getRawHeight();
+ final WindowManager win = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ final Display display = win.getDefaultDisplay();
+ final Point size = new Point();
+ display.getRealSize(size);
+ mMaximumDrawingCacheSize = 4 * size.x * size.y;
mOverscrollDistance = (int) (sizeAndDensity * OVERSCROLL_DISTANCE + 0.5f);
mOverflingDistance = (int) (sizeAndDensity * OVERFLING_DISTANCE + 0.5f);
if (!sHasPermanentMenuKeySet) {
- IWindowManager wm = Display.getWindowManager();
+ IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
try {
sHasPermanentMenuKey = !wm.hasSystemNavBar() && !wm.hasNavigationBar();
sHasPermanentMenuKeySet = true;
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index dd671dc..c013d85 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -255,6 +255,35 @@ public class ViewDebug {
boolean retrieveReturn() default false;
}
+ /**
+ * Allows a View to inject custom children into HierarchyViewer. For example,
+ * WebView uses this to add its internal layer tree as a child to itself
+ * @hide
+ */
+ public interface HierarchyHandler {
+ /**
+ * Dumps custom children to hierarchy viewer.
+ * See ViewDebug.dumpViewWithProperties(Context, View, BufferedWriter, int)
+ * for the format
+ *
+ * An empty implementation should simply do nothing
+ *
+ * @param out The output writer
+ * @param level The indentation level
+ */
+ public void dumpViewHierarchyWithProperties(BufferedWriter out, int level);
+
+ /**
+ * Returns a View to enable grabbing screenshots from custom children
+ * returned in dumpViewHierarchyWithProperties.
+ *
+ * @param className The className of the view to find
+ * @param hashCode The hashCode of the view to find
+ * @return the View to capture from, or null if not found
+ */
+ public View findHierarchyView(String className, int hashCode);
+ }
+
private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
@@ -468,8 +497,8 @@ public class ViewDebug {
throws IOException {
long durationMeasure =
- (root || (view.mPrivateFlags & View.MEASURED_DIMENSION_SET) != 0) ? profileViewOperation(
- view, new ViewOperation<Void>() {
+ (root || (view.mPrivateFlags & View.PFLAG_MEASURED_DIMENSION_SET) != 0)
+ ? profileViewOperation(view, new ViewOperation<Void>() {
public Void[] pre() {
forceLayout(view);
return null;
@@ -495,8 +524,8 @@ public class ViewDebug {
})
: 0;
long durationLayout =
- (root || (view.mPrivateFlags & View.LAYOUT_REQUIRED) != 0) ? profileViewOperation(
- view, new ViewOperation<Void>() {
+ (root || (view.mPrivateFlags & View.PFLAG_LAYOUT_REQUIRED) != 0)
+ ? profileViewOperation(view, new ViewOperation<Void>() {
public Void[] pre() {
return null;
}
@@ -509,15 +538,14 @@ public class ViewDebug {
}
}) : 0;
long durationDraw =
- (root || !view.willNotDraw() || (view.mPrivateFlags & View.DRAWN) != 0) ? profileViewOperation(
- view,
- new ViewOperation<Object>() {
+ (root || !view.willNotDraw() || (view.mPrivateFlags & View.PFLAG_DRAWN) != 0)
+ ? profileViewOperation(view, new ViewOperation<Object>() {
public Object[] pre() {
final DisplayMetrics metrics =
(view != null && view.getResources() != null) ?
view.getResources().getDisplayMetrics() : null;
final Bitmap bitmap = metrics != null ?
- Bitmap.createBitmap(metrics.widthPixels,
+ Bitmap.createBitmap(metrics, metrics.widthPixels,
metrics.heightPixels, Bitmap.Config.RGB_565) : null;
final Canvas canvas = bitmap != null ? new Canvas(bitmap) : null;
return new Object[] {
@@ -622,7 +650,7 @@ public class ViewDebug {
final boolean localVisible = view.getVisibility() == View.VISIBLE && visible;
- if ((view.mPrivateFlags & View.SKIP_DRAW) != View.SKIP_DRAW) {
+ if ((view.mPrivateFlags & View.PFLAG_SKIP_DRAW) != View.PFLAG_SKIP_DRAW) {
final int id = view.getId();
String name = view.getClass().getSimpleName();
if (id != View.NO_ID) {
@@ -677,7 +705,8 @@ public class ViewDebug {
Log.w("View", "Failed to create capture bitmap!");
// Send an empty one so that it doesn't get stuck waiting for
// something.
- b = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ b = Bitmap.createBitmap(root.getResources().getDisplayMetrics(),
+ 1, 1, Bitmap.Config.ARGB_8888);
}
BufferedOutputStream out = null;
@@ -759,6 +788,13 @@ public class ViewDebug {
} else if (isRequestedView(view, className, hashCode)) {
return view;
}
+ if (view instanceof HierarchyHandler) {
+ final View found = ((HierarchyHandler)view)
+ .findHierarchyView(className, hashCode);
+ if (found != null) {
+ return found;
+ }
+ }
}
return null;
@@ -783,6 +819,9 @@ public class ViewDebug {
dumpViewWithProperties(context, view, out, level + 1);
}
}
+ if (group instanceof HierarchyHandler) {
+ ((HierarchyHandler)group).dumpViewHierarchyWithProperties(out, level + 1);
+ }
}
private static boolean dumpViewWithProperties(Context context, View view,
@@ -1139,10 +1178,14 @@ public class ViewDebug {
private static void writeValue(BufferedWriter out, Object value) throws IOException {
if (value != null) {
- String output = value.toString().replace("\n", "\\n");
- out.write(String.valueOf(output.length()));
- out.write(",");
- out.write(output);
+ String output = "[EXCEPTION]";
+ try {
+ output = value.toString().replace("\n", "\\n");
+ } finally {
+ out.write(String.valueOf(output.length()));
+ out.write(",");
+ out.write(output);
+ }
} else {
out.write("4,null");
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 0c83a73..db1c00a 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -170,6 +170,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
+ @ViewDebug.ExportedProperty(flagMapping = {
+ @ViewDebug.FlagToString(mask = FLAG_CLIP_CHILDREN, equals = FLAG_CLIP_CHILDREN,
+ name = "CLIP_CHILDREN"),
+ @ViewDebug.FlagToString(mask = FLAG_CLIP_TO_PADDING, equals = FLAG_CLIP_TO_PADDING,
+ name = "CLIP_TO_PADDING"),
+ @ViewDebug.FlagToString(mask = FLAG_PADDING_NOT_NULL, equals = FLAG_PADDING_NOT_NULL,
+ name = "PADDING_NOT_NULL")
+ })
protected int mGroupFlags;
/*
@@ -404,10 +412,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// views during a transition when they otherwise would have become gone/invisible
private ArrayList<View> mVisibilityChangingChildren;
- // Indicates whether this container will use its children layers to draw
- @ViewDebug.ExportedProperty(category = "drawing")
- boolean mDrawLayers = true;
-
// Indicates how many of this container's child subtrees contain transient state
@ViewDebug.ExportedProperty(category = "layout")
private int mChildCountWithTransientState = 0;
@@ -628,11 +632,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* FOCUS_RIGHT, or 0 for not applicable.
*/
public View focusSearch(View focused, int direction) {
- // If we are moving accessibility focus we want to consider all
- // views no matter if they are on the screen. It is responsibility
- // of the accessibility service to check whether the result is in
- // the screen.
- if (isRootNamespace() && (direction & FOCUS_ACCESSIBILITY) == 0) {
+ if (isRootNamespace()) {
// root namespace means we should consider ourselves the top of the
// tree for focus searching; otherwise we could be focus searching
// into other tabs. see LocalActivityManager and TabHost for more info
@@ -804,7 +804,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
public boolean hasFocus() {
- return (mPrivateFlags & FOCUSED) != 0 || mFocused != null;
+ return (mPrivateFlags & PFLAG_FOCUSED) != 0 || mFocused != null;
}
/*
@@ -867,8 +867,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int descendantFocusability = getDescendantFocusability();
- if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS
- || (focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
+ if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
final int count = mChildrenCount;
final View[] children = mChildren;
@@ -886,9 +885,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// among the focusable children would be more interesting.
if (descendantFocusability != FOCUS_AFTER_DESCENDANTS
// No focusable descendants
- || (focusableCount == views.size())
- // We are collecting accessibility focusables.
- || (focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
+ || (focusableCount == views.size())) {
super.addFocusables(views, direction, focusableMode);
}
}
@@ -901,7 +898,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = 0; i < childrenCount; i++) {
View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- && (child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
+ && (child.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
child.findViewsWithText(outViews, text, flags);
}
}
@@ -1180,7 +1177,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View view = mCurrentDragView;
event.mAction = DragEvent.ACTION_DRAG_EXITED;
view.dispatchDragEvent(event);
- view.mPrivateFlags2 &= ~View.DRAG_HOVERED;
+ view.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
view.refreshDrawableState();
}
mCurrentDragView = target;
@@ -1189,7 +1186,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (target != null) {
event.mAction = DragEvent.ACTION_DRAG_ENTERED;
target.dispatchDragEvent(event);
- target.mPrivateFlags2 |= View.DRAG_HOVERED;
+ target.mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED;
target.refreshDrawableState();
}
event.mAction = action; // restore the event's original state
@@ -1223,7 +1220,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (mCurrentDragView != null) {
final View view = mCurrentDragView;
view.dispatchDragEvent(event);
- view.mPrivateFlags2 &= ~View.DRAG_HOVERED;
+ view.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
view.refreshDrawableState();
mCurrentDragView = null;
@@ -1284,7 +1281,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mDragNotifiedChildren.add(child);
canAccept = child.dispatchDragEvent(mCurrentDrag);
if (canAccept && !child.canAcceptDrag()) {
- child.mPrivateFlags2 |= View.DRAG_CAN_ACCEPT;
+ child.mPrivateFlags2 |= View.PFLAG2_DRAG_CAN_ACCEPT;
child.refreshDrawableState();
}
}
@@ -1333,9 +1330,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
- if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
return super.dispatchKeyEventPreIme(event);
- } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
return mFocused.dispatchKeyEventPreIme(event);
}
return false;
@@ -1350,11 +1349,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
- if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchKeyEvent(event)) {
return true;
}
- } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
@@ -1371,9 +1372,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
- if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
return super.dispatchKeyShortcutEvent(event);
- } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
return mFocused.dispatchKeyShortcutEvent(event);
}
return false;
@@ -1388,11 +1391,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mInputEventConsistencyVerifier.onTrackballEvent(event, 1);
}
- if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
if (super.dispatchTrackballEvent(event)) {
return true;
}
- } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
if (mFocused.dispatchTrackballEvent(event)) {
return true;
}
@@ -1658,20 +1663,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * @hide
- */
- @Override
- public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) {
- if (includeForAccessibility() && isActionableForAccessibility()) {
- return this;
- }
- if (mParent != null) {
- return mParent.findViewToTakeAccessibilityFocusFromHover(this, descendant);
- }
- return null;
- }
-
- /**
* Implement this method to intercept hover events before they are handled
* by child views.
* <p>
@@ -1732,8 +1723,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float x = event.getX();
final float y = event.getY();
+ final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
- final View child = children[i];
+ final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
+ final View child = children[childIndex];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
@@ -1755,9 +1748,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@Override
protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
// Send the event to the focused child or to this view group if it has focus.
- if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
+ if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
+ == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
return super.dispatchGenericFocusedEvent(event);
- } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+ } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
+ == PFLAG_HAS_BOUNDS) {
return mFocused.dispatchGenericMotionEvent(event);
}
return false;
@@ -1858,8 +1853,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
+ final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
- final View child = children[i];
+ final int childIndex = customOrder ?
+ getChildDrawingOrder(childrenCount, i) : i;
+ final View child = children[childIndex];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
@@ -1877,7 +1875,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
- mLastTouchDownIndex = i;
+ mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
@@ -1968,8 +1966,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* Returns true if the flag was previously set.
*/
private static boolean resetCancelNextUpFlag(View view) {
- if ((view.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- view.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
+ if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
+ view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
return true;
}
return false;
@@ -2535,11 +2533,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * {@inheritDoc}
+ * @hide
*/
@Override
- public void setPadding(int left, int top, int right, int bottom) {
- super.setPadding(left, top, right, bottom);
+ protected void internalSetPadding(int left, int top, int right, int bottom) {
+ super.internalSetPadding(left, top, right, bottom);
if ((mPaddingLeft | mPaddingTop | mPaddingRight | mPaddingBottom) != 0) {
mGroupFlags |= FLAG_PADDING_NOT_NULL;
@@ -2786,7 +2784,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
// We will draw our child's animation, let's reset the flag
- mPrivateFlags &= ~DRAW_ANIMATION;
+ mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
boolean more = false;
@@ -2906,8 +2904,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View child = children[i];
if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) &&
child.hasStaticLayer()) {
- child.mRecreateDisplayList = (child.mPrivateFlags & INVALIDATED) == INVALIDATED;
- child.mPrivateFlags &= ~INVALIDATED;
+ child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED)
+ == PFLAG_INVALIDATED;
+ child.mPrivateFlags &= ~PFLAG_INVALIDATED;
child.getDisplayList();
child.mRecreateDisplayList = false;
}
@@ -2930,45 +2929,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- *
- * @param enabled True if children should be drawn with layers, false otherwise.
- *
- * @hide
- */
- public void setChildrenLayersEnabled(boolean enabled) {
- if (enabled != mDrawLayers) {
- mDrawLayers = enabled;
- invalidate(true);
-
- boolean flushLayers = !enabled;
- AttachInfo info = mAttachInfo;
- if (info != null && info.mHardwareRenderer != null &&
- info.mHardwareRenderer.isEnabled()) {
- if (!info.mHardwareRenderer.validate()) {
- flushLayers = false;
- }
- } else {
- flushLayers = false;
- }
-
- // We need to invalidate any child with a layer. For instance,
- // if a child is backed by a hardware layer and we disable layers
- // the child is marked as not dirty (flags cleared the last time
- // the child was drawn inside its layer.) However, that child might
- // never have created its own display list or have an obsolete
- // display list. By invalidating the child we ensure the display
- // list is in sync with the content of the hardware layer.
- for (int i = 0; i < mChildrenCount; i++) {
- View child = mChildren[i];
- if (child.mLayerType != LAYER_TYPE_NONE) {
- if (flushLayers) child.flushLayer();
- child.invalidate(true);
- }
- }
- }
- }
-
- /**
* By default, children are clipped to their bounds before drawing. This
* allows view groups to override this behavior for animations, etc.
*
@@ -3052,7 +3012,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*
* @param enabled True to enable static transformations on children, false otherwise.
*
- * @see #FLAG_SUPPORT_STATIC_TRANSFORMATIONS
+ * @see #getChildStaticTransformation(View, android.view.animation.Transformation)
*/
protected void setStaticTransformationsEnabled(boolean enabled) {
setBooleanFlag(FLAG_SUPPORT_STATIC_TRANSFORMATIONS, enabled);
@@ -3062,7 +3022,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* Sets <code>t</code> to be the static transformation of the child, if set, returning a
* boolean to indicate whether a static transform was set. The default implementation
* simply returns <code>false</code>; subclasses may override this method for different
- * behavior.
+ * behavior. {@link #setStaticTransformationsEnabled(boolean)} must be set to true
+ * for this method to be called.
*
* @param child The child view whose static transform is being requested
* @param t The Transformation which will hold the result
@@ -3088,7 +3049,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = 0; i < len; i++) {
View v = where[i];
- if ((v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
+ if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);
if (v != null) {
@@ -3115,7 +3076,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = 0; i < len; i++) {
View v = where[i];
- if ((v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
+ if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewWithTag(tag);
if (v != null) {
@@ -3142,7 +3103,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = 0; i < len; i++) {
View v = where[i];
- if (v != childToSkip && (v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
+ if (v != childToSkip && (v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
v = v.findViewByPredicate(predicate);
if (v != null) {
@@ -3352,7 +3313,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
boolean preventRequestLayout) {
child.mParent = null;
addViewInner(child, index, params, preventRequestLayout);
- child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN;
+ child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
return true;
}
@@ -3362,7 +3323,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @param child the child on which to perform the cleanup
*/
protected void cleanupLayoutState(View child) {
- child.mPrivateFlags &= ~View.FORCE_LAYOUT;
+ child.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
}
private void addViewInner(View child, int index, LayoutParams params,
@@ -3421,6 +3382,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
ai.mKeepScreenOn = lastKeepOn;
}
+ if (child.isLayoutDirectionInherited()) {
+ child.resetResolvedLayoutDirection();
+ child.resolveRtlPropertiesIfNeeded();
+ }
+
onViewAdded(child);
if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
@@ -3655,6 +3621,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
childHasTransientStateChanged(view, false);
}
+ view.resetRtlProperties();
+
onViewRemoved(view);
needGlobalAttributesUpdate(false);
@@ -3838,6 +3806,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Finishes the removal of a detached view. This method will dispatch the detached from
* window event and notify the hierarchy change listener.
+ * <p>
+ * This method is intended to be lightweight and makes no assumptions about whether the
+ * parent or child should be redrawn. Proper use of this method will include also making
+ * any appropriate {@link #requestLayout()} or {@link #invalidate()} calls.
+ * For example, callers can {@link #post(Runnable) post} a {@link Runnable}
+ * which performs a {@link #requestLayout()} on the next frame, after all detach/remove
+ * calls are finished, causing layout to be run prior to redrawing the view hierarchy.
*
* @param child the child to be definitely removed from the view hierarchy
* @param animate if true and the view has an animation, the view is placed in the
@@ -3878,10 +3853,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Attaches a view to this view group. Attaching a view assigns this group as the parent,
- * sets the layout parameters and puts the view in the list of children so it can be retrieved
- * by calling {@link #getChildAt(int)}.
- *
- * This method should be called only for view which were detached from their parent.
+ * sets the layout parameters and puts the view in the list of children so that
+ * it can be retrieved by calling {@link #getChildAt(int)}.
+ * <p>
+ * This method is intended to be lightweight and makes no assumptions about whether the
+ * parent or child should be redrawn. Proper use of this method will include also making
+ * any appropriate {@link #requestLayout()} or {@link #invalidate()} calls.
+ * For example, callers can {@link #post(Runnable) post} a {@link Runnable}
+ * which performs a {@link #requestLayout()} on the next frame, after all detach/attach
+ * calls are finished, causing layout to be run prior to redrawing the view hierarchy.
+ * <p>
+ * This method should be called only for views which were detached from their parent.
*
* @param child the child to attach
* @param index the index at which the child should be attached
@@ -3902,9 +3884,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
addInArray(child, index);
child.mParent = this;
- child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK & ~DRAWING_CACHE_VALID) |
- DRAWN | INVALIDATED;
- this.mPrivateFlags |= INVALIDATED;
+ child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK
+ & ~PFLAG_DRAWING_CACHE_VALID)
+ | PFLAG_DRAWN | PFLAG_INVALIDATED;
+ this.mPrivateFlags |= PFLAG_INVALIDATED;
if (child.hasFocus()) {
requestChildFocus(child, child.findFocus());
@@ -3912,10 +3895,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Detaches a view from its parent. Detaching a view should be temporary and followed
- * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
- * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached,
- * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}.
+ * Detaches a view from its parent. Detaching a view should be followed
+ * either by a call to
+ * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+ * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
+ * temporary; reattachment or removal should happen within the same drawing cycle as
+ * detachment. When a view is detached, its parent is null and cannot be retrieved by a
+ * call to {@link #getChildAt(int)}.
*
* @param child the child to detach
*
@@ -3930,10 +3916,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Detaches a view from its parent. Detaching a view should be temporary and followed
- * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
- * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached,
- * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}.
+ * Detaches a view from its parent. Detaching a view should be followed
+ * either by a call to
+ * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+ * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
+ * temporary; reattachment or removal should happen within the same drawing cycle as
+ * detachment. When a view is detached, its parent is null and cannot be retrieved by a
+ * call to {@link #getChildAt(int)}.
*
* @param index the index of the child to detach
*
@@ -3948,10 +3937,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Detaches a range of view from their parent. Detaching a view should be temporary and followed
- * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
- * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached, its
- * parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}.
+ * Detaches a range of views from their parents. Detaching a view should be followed
+ * either by a call to
+ * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+ * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
+ * temporary; reattachment or removal should happen within the same drawing cycle as
+ * detachment. When a view is detached, its parent is null and cannot be retrieved by a
+ * call to {@link #getChildAt(int)}.
*
* @param start the first index of the childrend range to detach
* @param count the number of children to detach
@@ -3967,10 +3959,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Detaches all views from the parent. Detaching a view should be temporary and followed
- * either by a call to {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
- * or a call to {@link #removeDetachedView(View, boolean)}. When a view is detached,
- * its parent is null and cannot be retrieved by a call to {@link #getChildAt(int)}.
+ * Detaches all views from the parent. Detaching a view should be followed
+ * either by a call to
+ * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)}
+ * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be
+ * temporary; reattachment or removal should happen within the same drawing cycle as
+ * detachment. When a view is detached, its parent is null and cannot be retrieved by a
+ * call to {@link #getChildAt(int)}.
*
* @see #detachViewFromParent(View)
* @see #detachViewFromParent(int)
@@ -4005,7 +4000,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// If the child is drawing an animation, we want to copy this flag onto
// ourselves and the parent to make sure the invalidate request goes
// through
- final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION;
+ final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)
+ == PFLAG_DRAW_ANIMATION;
// Check whether the child that requests the invalidate is fully opaque
// Views being animated or transformed are not considered opaque because we may
@@ -4015,22 +4011,38 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
child.getAnimation() == null && childMatrix.isIdentity();
// Mark the child as dirty, using the appropriate flag
// Make sure we do not set both flags at the same time
- int opaqueFlag = isOpaque ? DIRTY_OPAQUE : DIRTY;
+ int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;
if (child.mLayerType != LAYER_TYPE_NONE) {
- mPrivateFlags |= INVALIDATED;
- mPrivateFlags &= ~DRAWING_CACHE_VALID;
+ mPrivateFlags |= PFLAG_INVALIDATED;
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
child.mLocalDirtyRect.union(dirty);
}
final int[] location = attachInfo.mInvalidateChildLocation;
location[CHILD_LEFT_INDEX] = child.mLeft;
location[CHILD_TOP_INDEX] = child.mTop;
- if (!childMatrix.isIdentity()) {
+ if (!childMatrix.isIdentity() ||
+ (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
- //boundingRect.inset(-0.5f, -0.5f);
- childMatrix.mapRect(boundingRect);
+ Matrix transformMatrix;
+ if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
+ Transformation t = attachInfo.mTmpTransformation;
+ boolean transformed = getChildStaticTransformation(child, t);
+ if (transformed) {
+ transformMatrix = attachInfo.mTmpMatrix;
+ transformMatrix.set(t.getMatrix());
+ if (!childMatrix.isIdentity()) {
+ transformMatrix.preConcat(childMatrix);
+ }
+ } else {
+ transformMatrix = childMatrix;
+ }
+ } else {
+ transformMatrix = childMatrix;
+ }
+ transformMatrix.mapRect(boundingRect);
dirty.set((int) (boundingRect.left - 0.5f),
(int) (boundingRect.top - 0.5f),
(int) (boundingRect.right + 0.5f),
@@ -4045,7 +4057,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (drawAnimation) {
if (view != null) {
- view.mPrivateFlags |= DRAW_ANIMATION;
+ view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
@@ -4056,10 +4068,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
- opaqueFlag = DIRTY;
+ opaqueFlag = PFLAG_DIRTY;
}
- if ((view.mPrivateFlags & DIRTY_MASK) != DIRTY) {
- view.mPrivateFlags = (view.mPrivateFlags & ~DIRTY_MASK) | opaqueFlag;
+ if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
+ view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
}
}
@@ -4090,8 +4102,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* does not intersect with this ViewGroup's bounds.
*/
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
- if ((mPrivateFlags & DRAWN) == DRAWN ||
- (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
+ if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
+ (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
FLAG_OPTIMIZE_INVALIDATE) {
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
@@ -4105,20 +4117,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
dirty.setEmpty();
}
}
- mPrivateFlags &= ~DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top;
if (mLayerType != LAYER_TYPE_NONE) {
- mPrivateFlags |= INVALIDATED;
+ mPrivateFlags |= PFLAG_INVALIDATED;
mLocalDirtyRect.union(dirty);
}
return mParent;
} else {
- mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
location[CHILD_LEFT_INDEX] = mLeft;
location[CHILD_TOP_INDEX] = mTop;
@@ -4130,7 +4142,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
if (mLayerType != LAYER_TYPE_NONE) {
- mPrivateFlags |= INVALIDATED;
+ mPrivateFlags |= PFLAG_INVALIDATED;
mLocalDirtyRect.union(dirty);
}
@@ -4192,8 +4204,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* coordinate system, pruning the invalidation if the parent has already been invalidated.
*/
private ViewParent invalidateChildInParentFast(int left, int top, final Rect dirty) {
- if ((mPrivateFlags & DRAWN) == DRAWN ||
- (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) {
+ if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
+ (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
dirty.offset(left - mScrollX, top - mScrollY);
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0 ||
@@ -4956,11 +4968,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
view.clearAnimation();
}
- if ((view.mPrivateFlags & ANIMATION_STARTED) == ANIMATION_STARTED) {
+ if ((view.mPrivateFlags & PFLAG_ANIMATION_STARTED) == PFLAG_ANIMATION_STARTED) {
view.onAnimationEnd();
// Should be performed by onAnimationEnd() but this avoid an infinite loop,
// so we'd rather be safe than sorry
- view.mPrivateFlags &= ~ANIMATION_STARTED;
+ view.mPrivateFlags &= ~PFLAG_ANIMATION_STARTED;
// Draw one more frame after the animation is done
mGroupFlags |= FLAG_INVALIDATE_REQUIRED;
}
@@ -5059,7 +5071,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@Override
public boolean gatherTransparentRegion(Region region) {
// If no transparent regions requested, we are always opaque.
- final boolean meOpaque = (mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) == 0;
+ final boolean meOpaque = (mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0;
if (meOpaque && region == null) {
// The caller doesn't care about the region, so stop now.
return true;
@@ -5084,7 +5096,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
public void requestTransparentRegion(View child) {
if (child != null) {
- child.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS;
+ child.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
if (mParent != null) {
mParent.requestTransparentRegion(this);
}
@@ -5249,12 +5261,67 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @hide
*/
@Override
- public void onResolvedLayoutDirectionReset() {
- // Take care of resetting the children resolution too
- final int count = getChildCount();
+ public boolean resolveLayoutDirection() {
+ final boolean result = super.resolveLayoutDirection();
+ if (result) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isLayoutDirectionInherited()) {
+ child.resolveLayoutDirection();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean resolveTextDirection() {
+ final boolean result = super.resolveTextDirection();
+ if (result) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isTextDirectionInherited()) {
+ child.resolveTextDirection();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean resolveTextAlignment() {
+ final boolean result = super.resolveTextAlignment();
+ if (result) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isTextAlignmentInherited()) {
+ child.resolveTextAlignment();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void resetResolvedLayoutDirection() {
+ super.resetResolvedLayoutDirection();
+
+ int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
- if (child.getLayoutDirection() == LAYOUT_DIRECTION_INHERIT) {
+ if (child.isLayoutDirectionInherited()) {
child.resetResolvedLayoutDirection();
}
}
@@ -5264,12 +5331,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @hide
*/
@Override
- public void onResolvedTextDirectionReset() {
- // Take care of resetting the children resolution too
- final int count = getChildCount();
+ public void resetResolvedTextDirection() {
+ super.resetResolvedTextDirection();
+
+ int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
- if (child.getTextDirection() == TEXT_DIRECTION_INHERIT) {
+ if (child.isTextDirectionInherited()) {
child.resetResolvedTextDirection();
}
}
@@ -5279,12 +5347,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @hide
*/
@Override
- public void onResolvedTextAlignmentReset() {
- // Take care of resetting the children resolution too
- final int count = getChildCount();
+ public void resetResolvedTextAlignment() {
+ super.resetResolvedTextAlignment();
+
+ int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
- if (child.getTextAlignment() == TEXT_ALIGNMENT_INHERIT) {
+ if (child.isTextAlignmentInherited()) {
child.resetResolvedTextAlignment();
}
}
@@ -5448,15 +5517,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
- * Extracts the layout parameters from the supplied attributes.
+ * Extracts the <code>width</code> and <code>height</code> layout parameters
+ * from the supplied TypedArray, <code>a</code>, and assigns them
+ * to the appropriate fields. If, <code>a</code>, does not contain an
+ * entry for either attribute, the value, {@link ViewGroup.LayoutParams#WRAP_CONTENT},
+ * is used as a default.
*
* @param a the style attributes to extract the parameters from
* @param widthAttr the identifier of the width attribute
* @param heightAttr the identifier of the height attribute
*/
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
- width = a.getLayoutDimension(widthAttr, "layout_width");
- height = a.getLayoutDimension(heightAttr, "layout_height");
+ width = a.getLayoutDimension(widthAttr, WRAP_CONTENT);
+ height = a.getLayoutDimension(heightAttr, WRAP_CONTENT);
}
/**
@@ -5468,7 +5541,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*
* {@link View#LAYOUT_DIRECTION_LTR}
* {@link View#LAYOUT_DIRECTION_RTL}
- * @hide
*/
public void onResolveLayoutDirection(int layoutDirection) {
}
@@ -5560,24 +5632,31 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* The start margin in pixels of the child.
* Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
* to this field.
- * @hide
*/
@ViewDebug.ExportedProperty(category = "layout")
- public int startMargin = DEFAULT_RELATIVE;
+ private int startMargin = DEFAULT_RELATIVE;
/**
* The end margin in pixels of the child.
* Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
* to this field.
- * @hide
*/
@ViewDebug.ExportedProperty(category = "layout")
- public int endMargin = DEFAULT_RELATIVE;
+ private int endMargin = DEFAULT_RELATIVE;
/**
* The default start and end margin.
+ * @hide
*/
- static private final int DEFAULT_RELATIVE = Integer.MIN_VALUE;
+ public static final int DEFAULT_RELATIVE = Integer.MIN_VALUE;
+
+ private int initialLeftMargin;
+ private int initialRightMargin;
+
+ private static int LAYOUT_DIRECTION_UNDEFINED = -1;
+
+ // Layout direction undefined by default
+ private int layoutDirection = LAYOUT_DIRECTION_UNDEFINED;
/**
* Creates a new set of layout parameters. The values are extracted from
@@ -5617,6 +5696,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
R.styleable.ViewGroup_MarginLayout_layout_marginEnd, DEFAULT_RELATIVE);
}
+ initialLeftMargin = leftMargin;
+ initialRightMargin = rightMargin;
+
a.recycle();
}
@@ -5642,6 +5724,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
this.bottomMargin = source.bottomMargin;
this.startMargin = source.startMargin;
this.endMargin = source.endMargin;
+
+ this.initialLeftMargin = source.leftMargin;
+ this.initialRightMargin = source.rightMargin;
+
+ setLayoutDirection(source.layoutDirection);
}
/**
@@ -5671,6 +5758,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
topMargin = top;
rightMargin = right;
bottomMargin = bottom;
+ initialLeftMargin = left;
+ initialRightMargin = right;
}
/**
@@ -5688,6 +5777,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop
* @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
* @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom
+ *
* @hide
*/
public void setMarginsRelative(int start, int top, int end, int bottom) {
@@ -5695,6 +5785,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
topMargin = top;
endMargin = end;
bottomMargin = bottom;
+ initialLeftMargin = 0;
+ initialRightMargin = 0;
+ }
+
+ /**
+ * Sets the relative start margin.
+ *
+ * @param start the start margin size
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+ */
+ public void setMarginStart(int start) {
+ startMargin = start;
}
/**
@@ -5703,10 +5806,27 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
*
* @return the start margin in pixels.
- * @hide
*/
public int getMarginStart() {
- return startMargin;
+ if (startMargin != DEFAULT_RELATIVE) return startMargin;
+ switch(layoutDirection) {
+ case View.LAYOUT_DIRECTION_RTL:
+ return rightMargin;
+ case View.LAYOUT_DIRECTION_LTR:
+ default:
+ return leftMargin;
+ }
+ }
+
+ /**
+ * Sets the relative end margin.
+ *
+ * @param end the end margin size
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+ */
+ public void setMarginEnd(int end) {
+ endMargin = end;
}
/**
@@ -5715,10 +5835,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
*
* @return the end margin in pixels.
- * @hide
*/
public int getMarginEnd() {
- return endMargin;
+ if (endMargin != DEFAULT_RELATIVE) return endMargin;
+ switch(layoutDirection) {
+ case View.LAYOUT_DIRECTION_RTL:
+ return leftMargin;
+ case View.LAYOUT_DIRECTION_LTR:
+ default:
+ return rightMargin;
+ }
}
/**
@@ -5727,29 +5853,53 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
* @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
*
- * @return true if either marginStart or marginEnd has been set
- * @hide
+ * @return true if either marginStart or marginEnd has been set.
*/
public boolean isMarginRelative() {
return (startMargin != DEFAULT_RELATIVE) || (endMargin != DEFAULT_RELATIVE);
}
/**
+ * Set the layout direction
+ * @param layoutDirection the layout direction.
+ * Should be either {@link View#LAYOUT_DIRECTION_LTR}
+ * or {@link View#LAYOUT_DIRECTION_RTL}.
+ */
+ public void setLayoutDirection(int layoutDirection) {
+ if (layoutDirection != View.LAYOUT_DIRECTION_LTR &&
+ layoutDirection != View.LAYOUT_DIRECTION_RTL) return;
+ this.layoutDirection = layoutDirection;
+ }
+
+ /**
+ * Retuns the layout direction. Can be either {@link View#LAYOUT_DIRECTION_LTR} or
+ * {@link View#LAYOUT_DIRECTION_RTL}.
+ *
+ * @return the layout direction.
+ */
+ public int getLayoutDirection() {
+ return layoutDirection;
+ }
+
+ /**
* This will be called by {@link android.view.View#requestLayout()}. Left and Right margins
* may be overridden depending on layout direction.
- * @hide
*/
@Override
public void onResolveLayoutDirection(int layoutDirection) {
+ setLayoutDirection(layoutDirection);
+
+ if (!isMarginRelative()) return;
+
switch(layoutDirection) {
case View.LAYOUT_DIRECTION_RTL:
- leftMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : leftMargin;
- rightMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : rightMargin;
+ leftMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : initialLeftMargin;
+ rightMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : initialRightMargin;
break;
case View.LAYOUT_DIRECTION_LTR:
default:
- leftMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : leftMargin;
- rightMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : rightMargin;
+ leftMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : initialLeftMargin;
+ rightMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : initialRightMargin;
break;
}
}
@@ -5757,6 +5907,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* @hide
*/
+ public boolean isLayoutRtl() {
+ return (layoutDirection == View.LAYOUT_DIRECTION_RTL);
+ }
+
+ /**
+ * @hide
+ */
@Override
public void onDebugDraw(View view, Canvas canvas) {
drawRect(canvas,
@@ -6080,7 +6237,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
view.getDrawingRect(viewLocation);
root.offsetDescendantRectToMyCoords(view, viewLocation);
mView = view;
- mLayoutDirection = root.getResolvedLayoutDirection();
+ mLayoutDirection = root.getLayoutDirection();
}
private void clear() {
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index d93b996..ddff91d 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -295,16 +295,4 @@ public interface ViewParent {
* @hide
*/
public void childAccessibilityStateChanged(View child);
-
- /**
- * A descendant requests this view to find a candidate to take accessibility
- * focus from hover.
- *
- * @param child The child making the call.
- * @param descendant The descendant that made the initial request.
- * @return A view to take accessibility focus.
- *
- * @hide
- */
- public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant);
}
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index ce6f4c5..d8db14c 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -1036,7 +1036,7 @@ public class ViewPropertyAnimator {
if ((propertyMask & TRANSFORM_MASK) != 0) {
mView.mTransformationInfo.mMatrixDirty = true;
if (!useDisplayListProperties) {
- mView.mPrivateFlags |= View.DRAWN; // force another invalidation
+ mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation
}
}
// invalidate(false) in all cases except if alphaHandled gets set to true
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ad850da..438f792 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -18,7 +18,6 @@ package android.view;
import android.Manifest;
import android.animation.LayoutTransition;
-import android.animation.ValueAnimator;
import android.app.ActivityManagerNative;
import android.content.ClipDescription;
import android.content.ComponentCallbacks;
@@ -53,6 +52,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
+import android.os.UserHandle;
import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -74,10 +74,9 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
import com.android.internal.policy.PolicyManager;
import com.android.internal.view.BaseSurfaceHolder;
-import com.android.internal.view.IInputMethodCallback;
-import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.RootViewSurfaceTaker;
import java.io.IOException;
@@ -89,7 +88,7 @@ import java.util.HashSet;
/**
* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager. This is for the most part an internal implementation
- * detail of {@link WindowManagerImpl}.
+ * detail of {@link WindowManagerGlobal}.
*
* {@hide}
*/
@@ -111,7 +110,7 @@ public final class ViewRootImpl implements ViewParent,
private static final boolean DEBUG_FPS = false;
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.
@@ -127,11 +126,6 @@ public final class ViewRootImpl implements ViewParent,
*/
static final int MAX_TRACKBALL_DELAY = 250;
- static IWindowSession sWindowSession;
-
- static final Object mStaticInit = new Object();
- static boolean mInitialized = false;
-
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>();
@@ -144,6 +138,9 @@ public final class ViewRootImpl implements ViewParent,
private static boolean sRenderThreadQueried = false;
private static final Object[] sRenderThreadQueryLock = new Object[0];
+ final IWindowSession mWindowSession;
+ final Display mDisplay;
+
long mLastTrackballTime = 0;
final TrackballAxis mTrackballAxisX = new TrackballAxis();
final TrackballAxis mTrackballAxisY = new TrackballAxis();
@@ -218,6 +215,9 @@ public final class ViewRootImpl implements ViewParent,
boolean mTraversalScheduled;
int mTraversalBarrier;
boolean mWillDrawSoon;
+ /** Set to true while in performTraversals for detecting when die(true) is called from internal
+ * callbacks such as onMeasure, onPreDraw, onDraw and deferring doDie() until later. */
+ boolean mIsInTraversal;
boolean mFitSystemWindowsRequested;
boolean mLayoutRequested;
boolean mFirst;
@@ -251,9 +251,7 @@ public final class ViewRootImpl implements ViewParent,
boolean mAdded;
boolean mAddedTouchMode;
- CompatibilityInfoHolder mCompatibilityInfo;
-
- /*package*/ int mAddNesting;
+ final CompatibilityInfoHolder mCompatibilityInfo;
// These are accessed by multiple threads.
final Rect mWinFrame; // frame given by window manager.
@@ -268,12 +266,6 @@ public final class ViewRootImpl implements ViewParent,
final Configuration mLastConfiguration = new Configuration();
final Configuration mPendingConfiguration = new Configuration();
- class ResizedInfo {
- Rect contentInsets;
- Rect visibleInsets;
- Configuration newConfig;
- }
-
boolean mScrollMayChange;
int mSoftInputMode;
View mLastScrolledFocus;
@@ -322,6 +314,7 @@ public final class ViewRootImpl implements ViewParent,
HashSet<View> mTempHashSet;
private final int mDensity;
+ private final int mNoncompatDensity;
/**
* Consistency verifier for debugging purposes.
@@ -330,24 +323,6 @@ public final class ViewRootImpl implements ViewParent,
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this, 0) : null;
- public static IWindowSession getWindowSession(Looper mainLooper) {
- synchronized (mStaticInit) {
- if (!mInitialized) {
- try {
- InputMethodManager imm = InputMethodManager.getInstance(mainLooper);
- IWindowManager windowManager = Display.getWindowManager();
- sWindowSession = windowManager.openSession(
- imm.getClient(), imm.getInputContext());
- float animatorScale = windowManager.getAnimationScale(2);
- ValueAnimator.setDurationScale(animatorScale);
- mInitialized = true;
- } catch (RemoteException e) {
- }
- }
- return sWindowSession;
- }
- }
-
static final class SystemUiVisibilityInfo {
int seq;
int globalVisibility;
@@ -355,7 +330,7 @@ public final class ViewRootImpl implements ViewParent,
int localChanges;
}
- public ViewRootImpl(Context context) {
+ public ViewRootImpl(Context context, Display display) {
super();
if (MEASURE_LATENCY) {
@@ -367,7 +342,11 @@ public final class ViewRootImpl implements ViewParent,
// Initialize the statics when this class is first instantiated. This is
// done here instead of in the static block because Zygote does not
// allow the spawning of threads.
- getWindowSession(context.getMainLooper());
+ mWindowSession = WindowManagerGlobal.getWindowSession(context.getMainLooper());
+ mDisplay = display;
+
+ CompatibilityInfoHolder cih = display.getCompatibilityInfo();
+ mCompatibilityInfo = cih != null ? cih : new CompatibilityInfoHolder();
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
@@ -391,9 +370,10 @@ public final class ViewRootImpl implements ViewParent,
new AccessibilityInteractionConnectionManager();
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
- mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, mHandler, this);
+ mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
+ mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
mProfileRendering = Boolean.parseBoolean(
SystemProperties.get(PROPERTY_PROFILE_RENDERING, "false"));
@@ -467,9 +447,10 @@ public final class ViewRootImpl implements ViewParent,
* @hide
*/
static boolean isInTouchMode() {
- if (mInitialized) {
+ IWindowSession windowSession = WindowManagerGlobal.peekWindowSession();
+ if (windowSession != null) {
try {
- return sWindowSession.getInTouchMode();
+ return windowSession.getInTouchMode();
} catch (RemoteException e) {
}
}
@@ -548,9 +529,9 @@ public final class ViewRootImpl implements ViewParent,
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
- res = sWindowSession.add(mWindow, mSeq, mWindowAttributes,
- getHostVisibility(), mAttachInfo.mContentInsets,
- mInputChannel);
+ res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
+ getHostVisibility(), mDisplay.getDisplayId(),
+ mAttachInfo.mContentInsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
@@ -572,7 +553,7 @@ public final class ViewRootImpl implements ViewParent,
mPendingContentInsets.set(mAttachInfo.mContentInsets);
mPendingVisibleInsets.set(0, 0, 0, 0);
if (DEBUG_LAYOUT) Log.v(TAG, "Added window " + mWindow);
- if (res < WindowManagerImpl.ADD_OKAY) {
+ if (res < WindowManagerGlobal.ADD_OKAY) {
mView = null;
mAttachInfo.mRootView = null;
mAdded = false;
@@ -580,33 +561,33 @@ public final class ViewRootImpl implements ViewParent,
unscheduleTraversals();
setAccessibilityFocus(null, null);
switch (res) {
- case WindowManagerImpl.ADD_BAD_APP_TOKEN:
- case WindowManagerImpl.ADD_BAD_SUBWINDOW_TOKEN:
- throw new WindowManagerImpl.BadTokenException(
+ case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
+ case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
+ throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
- case WindowManagerImpl.ADD_NOT_APP_TOKEN:
- throw new WindowManagerImpl.BadTokenException(
+ case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
+ throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not for an application");
- case WindowManagerImpl.ADD_APP_EXITING:
- throw new WindowManagerImpl.BadTokenException(
+ case WindowManagerGlobal.ADD_APP_EXITING:
+ throw new WindowManager.BadTokenException(
"Unable to add window -- app for token " + attrs.token
+ " is exiting");
- case WindowManagerImpl.ADD_DUPLICATE_ADD:
- throw new WindowManagerImpl.BadTokenException(
+ case WindowManagerGlobal.ADD_DUPLICATE_ADD:
+ throw new WindowManager.BadTokenException(
"Unable to add window -- window " + mWindow
+ " has already been added");
- case WindowManagerImpl.ADD_STARTING_NOT_NEEDED:
+ case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
// Silently ignore -- we would have just removed it
// right away, anyway.
return;
- case WindowManagerImpl.ADD_MULTIPLE_SINGLETON:
- throw new WindowManagerImpl.BadTokenException(
+ case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
+ throw new WindowManager.BadTokenException(
"Unable to add window " + mWindow +
" -- another window of this type already exists");
- case WindowManagerImpl.ADD_PERMISSION_DENIED:
- throw new WindowManagerImpl.BadTokenException(
+ case WindowManagerGlobal.ADD_PERMISSION_DENIED:
+ throw new WindowManager.BadTokenException(
"Unable to add window " + mWindow +
" -- permission denied for this window type");
}
@@ -629,8 +610,8 @@ public final class ViewRootImpl implements ViewParent,
}
view.assignParent(this);
- mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0;
- mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0;
+ mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
+ mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;
if (mAccessibilityManager.isEnabled()) {
mAccessibilityInteractionConnectionManager.ensureConnection();
@@ -673,6 +654,12 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ void pushHardwareLayerUpdate(HardwareLayer layer) {
+ if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
+ mAttachInfo.mHardwareRenderer.pushLayerUpdate(layer);
+ }
+ }
+
public boolean attachFunctor(int functor) {
//noinspection SimplifiableIfStatement
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
@@ -852,8 +839,8 @@ public final class ViewRootImpl implements ViewParent,
void invalidateWorld(View view) {
view.invalidate();
if (view instanceof ViewGroup) {
- ViewGroup parent = (ViewGroup)view;
- for (int i=0; i<parent.getChildCount(); i++) {
+ ViewGroup parent = (ViewGroup) view;
+ for (int i = 0; i < parent.getChildCount(); i++) {
invalidateWorld(parent.getChildAt(i));
}
}
@@ -1027,17 +1014,15 @@ public final class ViewRootImpl implements ViewParent,
//Log.i(TAG, "Computing view hierarchy attributes!");
attachInfo.mRecomputeGlobalAttributes = false;
boolean oldScreenOn = attachInfo.mKeepScreenOn;
- int oldVis = attachInfo.mSystemUiVisibility;
- boolean oldHasSystemUiListeners = attachInfo.mHasSystemUiListeners;
attachInfo.mKeepScreenOn = false;
attachInfo.mSystemUiVisibility = 0;
attachInfo.mHasSystemUiListeners = false;
mView.dispatchCollectViewAttributes(attachInfo, 0);
attachInfo.mSystemUiVisibility &= ~attachInfo.mDisabledSystemUiVisibility;
+ WindowManager.LayoutParams params = mWindowAttributes;
if (attachInfo.mKeepScreenOn != oldScreenOn
- || attachInfo.mSystemUiVisibility != oldVis
- || attachInfo.mHasSystemUiListeners != oldHasSystemUiListeners) {
- WindowManager.LayoutParams params = mWindowAttributes;
+ || attachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility
+ || attachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) {
applyKeepScreenOnFlag(params);
params.subtreeSystemUiVisibility = attachInfo.mSystemUiVisibility;
params.hasSystemUiListeners = attachInfo.mHasSystemUiListeners;
@@ -1127,6 +1112,7 @@ public final class ViewRootImpl implements ViewParent,
if (host == null || !mAdded)
return;
+ mIsInTraversal = true;
mWillDrawSoon = true;
boolean windowSizeMayChange = false;
boolean newSurface = false;
@@ -1171,9 +1157,8 @@ public final class ViewRootImpl implements ViewParent,
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL) {
// NOTE -- system code, won't try to do compat mode.
- Display disp = WindowManagerImpl.getDefault().getDefaultDisplay();
Point size = new Point();
- disp.getRealSize(size);
+ mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
@@ -1258,9 +1243,8 @@ public final class ViewRootImpl implements ViewParent,
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL) {
// NOTE -- system code, won't try to do compat mode.
- Display disp = WindowManagerImpl.getDefault().getDefaultDisplay();
Point size = new Point();
- disp.getRealSize(size);
+ mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
@@ -1310,7 +1294,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
- if (params != null && (host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) {
+ if (params != null && (host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
if (!PixelFormat.formatHasAlpha(params.format)) {
params.format = PixelFormat.TRANSLUCENT;
}
@@ -1425,6 +1409,7 @@ public final class ViewRootImpl implements ViewParent,
mResizeBuffer.getHeight() != mHeight) {
mResizeBuffer.resize(mWidth, mHeight);
}
+ // TODO: should handle create/resize failure
layerCanvas = mResizeBuffer.start(hwRendererCanvas);
layerCanvas.setViewport(mWidth, mHeight);
layerCanvas.onPreDraw(null);
@@ -1504,11 +1489,13 @@ public final class ViewRootImpl implements ViewParent,
if (mAttachInfo.mHardwareRenderer != null) {
try {
- hwInitialized = mAttachInfo.mHardwareRenderer.initialize(mHolder);
+ hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
+ mHolder.getSurface());
} catch (Surface.OutOfResourcesException e) {
Log.e(TAG, "OutOfResourcesException initializing HW surface", e);
try {
- if (!sWindowSession.outOfMemory(mWindow)) {
+ if (!mWindowSession.outOfMemory(mWindow) &&
+ Process.myUid() != Process.SYSTEM_UID) {
Slog.w(TAG, "No processes killed for memory; killing self");
Process.killProcess(Process.myPid());
}
@@ -1537,11 +1524,11 @@ public final class ViewRootImpl implements ViewParent,
mSurfaceHolder == null && mAttachInfo.mHardwareRenderer != null) {
mFullRedrawNeeded = true;
try {
- mAttachInfo.mHardwareRenderer.updateSurface(mHolder);
+ mAttachInfo.mHardwareRenderer.updateSurface(mHolder.getSurface());
} catch (Surface.OutOfResourcesException e) {
Log.e(TAG, "OutOfResourcesException updating HW surface", e);
try {
- if (!sWindowSession.outOfMemory(mWindow)) {
+ if (!mWindowSession.outOfMemory(mWindow)) {
Slog.w(TAG, "No processes killed for memory; killing self");
Process.killProcess(Process.myPid());
}
@@ -1628,14 +1615,15 @@ public final class ViewRootImpl implements ViewParent,
mHeight != mAttachInfo.mHardwareRenderer.getHeight()) {
mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight);
if (!hwInitialized) {
- mAttachInfo.mHardwareRenderer.invalidate(mHolder);
+ mAttachInfo.mHardwareRenderer.invalidate(mHolder.getSurface());
+ mFullRedrawNeeded = true;
}
}
}
if (!mStopped) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
- (relayoutResult&WindowManagerImpl.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
+ (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
@@ -1680,6 +1668,30 @@ public final class ViewRootImpl implements ViewParent,
layoutRequested = true;
}
}
+ } else {
+ // Not the first pass and no window/insets/visibility change but the window
+ // may have moved and we need check that and if so to update the left and right
+ // in the attach info. We translate only the window frame since on window move
+ // the window manager tells us only for the new frame but the insets are the
+ // same and we do not want to translate them more than once.
+
+ // TODO: Well, we are checking whether the frame has changed similarly
+ // to how this is done for the insets. This is however incorrect since
+ // the insets and the frame are translated. For example, the old frame
+ // was (1, 1 - 1, 1) and was translated to say (2, 2 - 2, 2), now the new
+ // reported frame is (2, 2 - 2, 2) which implies no change but this is not
+ // true since we are comparing a not translated value to a translated one.
+ // This scenario is rare but we may want to fix that.
+
+ final boolean windowMoved = (attachInfo.mWindowLeft != frame.left
+ || attachInfo.mWindowTop != frame.top);
+ if (windowMoved) {
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWinFrame(frame);
+ }
+ attachInfo.mWindowLeft = frame.left;
+ attachInfo.mWindowTop = frame.top;
+ }
}
final boolean didLayout = layoutRequested && !mStopped;
@@ -1691,7 +1703,7 @@ public final class ViewRootImpl implements ViewParent,
// By this point all views have been sized and positionned
// We can compute the transparent area
- if ((host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) {
+ if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
host.getLocationInWindow(mTmpLocation);
@@ -1708,7 +1720,7 @@ public final class ViewRootImpl implements ViewParent,
mPreviousTransparentRegion.set(mTransparentRegion);
// reconfigure window manager
try {
- sWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
+ mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
} catch (RemoteException e) {
}
}
@@ -1757,7 +1769,7 @@ public final class ViewRootImpl implements ViewParent,
}
try {
- sWindowSession.setInsets(mWindow, insets.mTouchableInsets,
+ mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
contentInsets, visibleInsets, touchableRegion);
} catch (RemoteException e) {
}
@@ -1782,7 +1794,7 @@ public final class ViewRootImpl implements ViewParent,
+ mRealFocusedView);
}
}
- if ((relayoutResult&WindowManagerImpl.RELAYOUT_RES_ANIMATING) != 0) {
+ if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0) {
// The first time we relayout the window, if the system is
// doing window animations, we want to hold of on any future
// draws until the animation is done.
@@ -1813,7 +1825,7 @@ public final class ViewRootImpl implements ViewParent,
}
// Remember if we must report the next draw.
- if ((relayoutResult & WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) {
+ if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mReportNextDraw = true;
}
@@ -1842,6 +1854,8 @@ public final class ViewRootImpl implements ViewParent,
mPendingTransitions.clear();
}
}
+
+ mIsInTraversal = false;
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
@@ -1875,7 +1889,7 @@ public final class ViewRootImpl implements ViewParent,
// the test below should not fail unless someone is messing with us
checkThread();
if (mView == child) {
- mView.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS;
+ mView.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
// Need to make sure we re-evaluate the window attributes next
// time around, to ensure the window has the correct format.
mWindowAttributesChanged = true;
@@ -2043,7 +2057,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
try {
- sWindowSession.finishDrawing(mWindow);
+ mWindowSession.finishDrawing(mWindow);
} catch (RemoteException e) {
}
}
@@ -2199,7 +2213,7 @@ public final class ViewRootImpl implements ViewParent,
} catch (Surface.OutOfResourcesException e) {
Log.e(TAG, "OutOfResourcesException locking surface", e);
try {
- if (!sWindowSession.outOfMemory(mWindow)) {
+ if (!mWindowSession.outOfMemory(mWindow)) {
Slog.w(TAG, "No processes killed for memory; killing self");
Process.killProcess(Process.myPid());
}
@@ -2238,7 +2252,7 @@ public final class ViewRootImpl implements ViewParent,
dirty.setEmpty();
mIsAnimating = false;
attachInfo.mDrawingTime = SystemClock.uptimeMillis();
- mView.mPrivateFlags |= View.DRAWN;
+ mView.mPrivateFlags |= View.PFLAG_DRAWN;
if (DEBUG_DRAW) {
Context cxt = mView.getContext();
@@ -2251,8 +2265,7 @@ public final class ViewRootImpl implements ViewParent,
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
- canvas.setScreenDensity(scalingRequired
- ? DisplayMetrics.DENSITY_DEVICE : 0);
+ canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
mView.draw(canvas);
@@ -2281,14 +2294,6 @@ public final class ViewRootImpl implements ViewParent,
return true;
}
- @Override
- public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) {
- if (descendant.includeForAccessibility()) {
- return descendant;
- }
- return null;
- }
-
/**
* We want to draw a highlight around the current accessibility focused.
* Since adding a style for all possible view is not a viable option we
@@ -2637,7 +2642,7 @@ public final class ViewRootImpl implements ViewParent,
mInputEventReceiver = null;
}
try {
- sWindowSession.remove(mWindow);
+ mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
@@ -2660,7 +2665,7 @@ public final class ViewRootImpl implements ViewParent,
CompatibilityInfo ci = mCompatibilityInfo.getIfNeeded();
if (ci != null) {
config = new Configuration(config);
- ci.applyToConfiguration(config);
+ ci.applyToConfiguration(mNoncompatDensity, config);
}
synchronized (sConfigCallbacks) {
@@ -2671,7 +2676,7 @@ public final class ViewRootImpl implements ViewParent,
if (mView != null) {
// At this point the resources have been updated to
// have the most recent config, whatever that is. Use
- // the on in them which may be newer.
+ // the one in them which may be newer.
config = mView.getResources().getConfiguration();
if (force || mLastConfiguration.diff(config) != 0) {
mLastConfiguration.setTo(config);
@@ -2727,6 +2732,7 @@ public final class ViewRootImpl implements ViewParent,
private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 22;
private final static int MSG_DISPATCH_DONE_ANIMATING = 23;
private final static int MSG_INVALIDATE_WORLD = 24;
+ private final static int MSG_WINDOW_MOVED = 25;
final class ViewRootHandler extends Handler {
@Override
@@ -2778,6 +2784,8 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST";
case MSG_DISPATCH_DONE_ANIMATING:
return "MSG_DISPATCH_DONE_ANIMATING";
+ case MSG_WINDOW_MOVED:
+ return "MSG_WINDOW_MOVED";
}
return super.getMessageName(message);
}
@@ -2806,28 +2814,31 @@ public final class ViewRootImpl implements ViewParent,
case MSG_DISPATCH_GET_NEW_SURFACE:
handleGetNewSurface();
break;
- case MSG_RESIZED:
- ResizedInfo ri = (ResizedInfo)msg.obj;
-
- if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2
- && mPendingContentInsets.equals(ri.contentInsets)
- && mPendingVisibleInsets.equals(ri.visibleInsets)
- && ((ResizedInfo)msg.obj).newConfig == null) {
+ case MSG_RESIZED: {
+ // Recycled in the fall through...
+ SomeArgs args = (SomeArgs) msg.obj;
+ if (mWinFrame.equals((Rect) args.arg1)
+ && mPendingContentInsets.equals((Rect) args.arg2)
+ && mPendingVisibleInsets.equals((Rect) args.arg3)
+ && ((Configuration) args.arg4 == null)) {
break;
}
- // fall through...
+ } // fall through...
case MSG_RESIZED_REPORT:
if (mAdded) {
- Configuration config = ((ResizedInfo)msg.obj).newConfig;
+ SomeArgs args = (SomeArgs) msg.obj;
+
+ Configuration config = (Configuration) args.arg4;
if (config != null) {
updateConfiguration(config, false);
}
- mWinFrame.left = 0;
- mWinFrame.right = msg.arg1;
- mWinFrame.top = 0;
- mWinFrame.bottom = msg.arg2;
- mPendingContentInsets.set(((ResizedInfo)msg.obj).contentInsets);
- mPendingVisibleInsets.set(((ResizedInfo)msg.obj).visibleInsets);
+
+ mWinFrame.set((Rect) args.arg1);
+ mPendingContentInsets.set((Rect) args.arg2);
+ mPendingVisibleInsets.set((Rect) args.arg3);
+
+ args.recycle();
+
if (msg.what == MSG_RESIZED_REPORT) {
mReportNextDraw = true;
}
@@ -2835,6 +2846,24 @@ public final class ViewRootImpl implements ViewParent,
if (mView != null) {
forceLayout(mView);
}
+
+ requestLayout();
+ }
+ break;
+ case MSG_WINDOW_MOVED:
+ if (mAdded) {
+ final int w = mWinFrame.width();
+ final int h = mWinFrame.height();
+ final int l = msg.arg1;
+ final int t = msg.arg2;
+ mWinFrame.left = l;
+ mWinFrame.right = l + w;
+ mWinFrame.top = t;
+ mWinFrame.bottom = t + h;
+
+ if (mView != null) {
+ forceLayout(mView);
+ }
requestLayout();
}
break;
@@ -2854,11 +2883,11 @@ public final class ViewRootImpl implements ViewParent,
mFullRedrawNeeded = true;
try {
mAttachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
- mHolder);
+ mHolder.getSurface());
} catch (Surface.OutOfResourcesException e) {
Log.e(TAG, "OutOfResourcesException locking surface", e);
try {
- if (!sWindowSession.outOfMemory(mWindow)) {
+ if (!mWindowSession.outOfMemory(mWindow)) {
Slog.w(TAG, "No processes killed for memory; killing self");
Process.killProcess(Process.myPid());
}
@@ -2979,7 +3008,9 @@ public final class ViewRootImpl implements ViewParent,
handleDispatchDoneAnimating();
} break;
case MSG_INVALIDATE_WORLD: {
- invalidateWorld(mView);
+ if (mView != null) {
+ invalidateWorld(mView);
+ }
} break;
}
}
@@ -3003,7 +3034,7 @@ public final class ViewRootImpl implements ViewParent,
// tell the window manager
try {
- sWindowSession.setInTouchMode(inTouchMode);
+ mWindowSession.setInTouchMode(inTouchMode);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -3190,6 +3221,33 @@ public final class ViewRootImpl implements ViewParent,
mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
}
+ if (mView != null && mAdded && (q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
+ if (LOCAL_LOGV)
+ Log.v(TAG, "Dispatching trackball " + event + " to " + mView);
+
+ // Dispatch to the IME before propagating down the view hierarchy.
+ // The IME will eventually call back into handleImeFinishedEvent.
+ if (mLastWasImTarget) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ final int seq = event.getSequenceNumber();
+ if (DEBUG_IMF)
+ Log.v(TAG, "Sending trackball event to IME: seq="
+ + seq + " event=" + event);
+ imm.dispatchTrackballEvent(mView.getContext(), seq, event,
+ mInputMethodCallback);
+ return;
+ }
+ }
+ }
+
+ // Not dispatching to IME, continue with post IME actions.
+ deliverTrackballEventPostIme(q);
+ }
+
+ private void deliverTrackballEventPostIme(QueuedInputEvent q) {
+ final MotionEvent event = (MotionEvent) q.mEvent;
+
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishInputEvent(q, false);
@@ -3323,8 +3381,33 @@ public final class ViewRootImpl implements ViewParent,
mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
}
- final int source = event.getSource();
- final boolean isJoystick = (source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0;
+ if (mView != null && mAdded && (q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
+ if (LOCAL_LOGV)
+ Log.v(TAG, "Dispatching generic motion " + event + " to " + mView);
+
+ // Dispatch to the IME before propagating down the view hierarchy.
+ // The IME will eventually call back into handleImeFinishedEvent.
+ if (mLastWasImTarget) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ final int seq = event.getSequenceNumber();
+ if (DEBUG_IMF)
+ Log.v(TAG, "Sending generic motion event to IME: seq="
+ + seq + " event=" + event);
+ imm.dispatchGenericMotionEvent(mView.getContext(), seq, event,
+ mInputMethodCallback);
+ return;
+ }
+ }
+ }
+
+ // Not dispatching to IME, continue with post IME actions.
+ deliverGenericMotionEventPostIme(q);
+ }
+
+ private void deliverGenericMotionEventPostIme(QueuedInputEvent q) {
+ final MotionEvent event = (MotionEvent) q.mEvent;
+ final boolean isJoystick = (event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0;
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
@@ -3345,7 +3428,8 @@ public final class ViewRootImpl implements ViewParent,
}
if (isJoystick) {
- // Translate the joystick event into DPAD keys and try to deliver those.
+ // Translate the joystick event into DPAD keys and try to deliver
+ // those.
updateJoystickDirection(event, true);
finishInputEvent(q, true);
} else {
@@ -3500,13 +3584,7 @@ public final class ViewRootImpl implements ViewParent,
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
- if ((q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
- // If there is no view, then the event will not be handled.
- if (mView == null || !mAdded) {
- finishInputEvent(q, false);
- return;
- }
-
+ if (mView != null && mAdded && (q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView);
// Perform predispatching before the IME.
@@ -3536,15 +3614,46 @@ public final class ViewRootImpl implements ViewParent,
void handleImeFinishedEvent(int seq, boolean handled) {
final QueuedInputEvent q = mCurrentInputEvent;
if (q != null && q.mEvent.getSequenceNumber() == seq) {
- final KeyEvent event = (KeyEvent)q.mEvent;
if (DEBUG_IMF) {
Log.v(TAG, "IME finished event: seq=" + seq
- + " handled=" + handled + " event=" + event);
+ + " handled=" + handled + " event=" + q);
}
if (handled) {
finishInputEvent(q, true);
} else {
- deliverKeyEventPostIme(q);
+ if (q.mEvent instanceof KeyEvent) {
+ KeyEvent event = (KeyEvent)q.mEvent;
+ if (event.getAction() != KeyEvent.ACTION_UP) {
+ // If the window doesn't currently have input focus, then drop
+ // this event. This could be an event that came back from the
+ // IME dispatch but the window has lost focus in the meantime.
+ if (!mAttachInfo.mHasWindowFocus) {
+ Slog.w(TAG, "Dropping event due to no window focus: " + event);
+ finishInputEvent(q, true);
+ return;
+ }
+ }
+ deliverKeyEventPostIme(q);
+ } else {
+ MotionEvent event = (MotionEvent)q.mEvent;
+ if (event.getAction() != MotionEvent.ACTION_CANCEL
+ && event.getAction() != MotionEvent.ACTION_UP) {
+ // If the window doesn't currently have input focus, then drop
+ // this event. This could be an event that came back from the
+ // IME dispatch but the window has lost focus in the meantime.
+ if (!mAttachInfo.mHasWindowFocus) {
+ Slog.w(TAG, "Dropping event due to no window focus: " + event);
+ finishInputEvent(q, true);
+ return;
+ }
+ }
+ final int source = q.mEvent.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ deliverTrackballEventPostIme(q);
+ } else {
+ deliverGenericMotionEventPostIme(q);
+ }
+ }
}
} else {
if (DEBUG_IMF) {
@@ -3717,10 +3826,10 @@ public final class ViewRootImpl implements ViewParent,
if (prevDragView != mCurrentDragView) {
try {
if (prevDragView != null) {
- sWindowSession.dragRecipientExited(mWindow);
+ mWindowSession.dragRecipientExited(mWindow);
}
if (mCurrentDragView != null) {
- sWindowSession.dragRecipientEntered(mWindow);
+ mWindowSession.dragRecipientEntered(mWindow);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to note drag target change");
@@ -3732,7 +3841,7 @@ public final class ViewRootImpl implements ViewParent,
mDragDescription = null;
try {
Log.i(TAG, "Reporting drop result: " + result);
- sWindowSession.reportDropResult(mWindow, result);
+ mWindowSession.reportDropResult(mWindow, result);
} catch (RemoteException e) {
Log.e(TAG, "Unable to report drop result");
}
@@ -3834,11 +3943,11 @@ public final class ViewRootImpl implements ViewParent,
params.type = mOrigWindowType;
}
}
- int relayoutResult = sWindowSession.relayout(
+ int relayoutResult = mWindowSession.relayout(
mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f),
- viewVisibility, insetsPending ? WindowManagerImpl.RELAYOUT_INSETS_PENDING : 0,
+ viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
mWinFrame, mPendingContentInsets, mPendingVisibleInsets,
mPendingConfiguration, mSurface);
//Log.d(TAG, "<<<<<< BACK FROM relayout");
@@ -3895,7 +4004,7 @@ public final class ViewRootImpl implements ViewParent,
*/
public boolean performHapticFeedback(int effectId, boolean always) {
try {
- return sWindowSession.performHapticFeedback(mWindow, effectId, always);
+ return mWindowSession.performHapticFeedback(mWindow, effectId, always);
} catch (RemoteException e) {
return false;
}
@@ -3941,7 +4050,9 @@ public final class ViewRootImpl implements ViewParent,
}
public void die(boolean immediate) {
- if (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();
} else {
if (!mIsDrawing) {
@@ -3974,8 +4085,8 @@ public final class ViewRootImpl implements ViewParent,
// animation info.
try {
if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
- & WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) {
- sWindowSession.finishDrawing(mWindow);
+ & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
+ mWindowSession.finishDrawing(mWindow);
}
} catch (RemoteException e) {
}
@@ -4034,26 +4145,37 @@ public final class ViewRootImpl implements ViewParent,
mHandler.sendMessage(msg);
}
- public void dispatchResized(int w, int h, Rect contentInsets,
+ public void dispatchResized(Rect frame, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
- if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": w=" + w
- + " h=" + h + " contentInsets=" + contentInsets.toShortString()
+ if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": frame=" + frame.toShortString()
+ + " contentInsets=" + contentInsets.toShortString()
+ " visibleInsets=" + visibleInsets.toShortString()
+ " reportDraw=" + reportDraw);
- Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT :MSG_RESIZED);
+ Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWindow(frame);
mTranslator.translateRectInScreenToAppWindow(contentInsets);
mTranslator.translateRectInScreenToAppWindow(visibleInsets);
- w *= mTranslator.applicationInvertedScale;
- h *= mTranslator.applicationInvertedScale;
- }
- msg.arg1 = w;
- msg.arg2 = h;
- ResizedInfo ri = new ResizedInfo();
- ri.contentInsets = new Rect(contentInsets);
- ri.visibleInsets = new Rect(visibleInsets);
- ri.newConfig = newConfig;
- msg.obj = ri;
+ }
+ SomeArgs args = SomeArgs.obtain();
+ final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
+ args.arg1 = sameProcessCall ? new Rect(frame) : frame;
+ args.arg2 = sameProcessCall ? new Rect(contentInsets) : contentInsets;
+ args.arg3 = sameProcessCall ? new Rect(visibleInsets) : visibleInsets;
+ args.arg4 = sameProcessCall && newConfig != null ? new Configuration(newConfig) : newConfig;
+ msg.obj = args;
+ mHandler.sendMessage(msg);
+ }
+
+ public void dispatchMoved(int newX, int newY) {
+ if (DEBUG_LAYOUT) Log.v(TAG, "Window moved " + this + ": newX=" + newX + " newY=" + newY);
+ if (mTranslator != null) {
+ PointF point = new PointF(newX, newY);
+ mTranslator.translatePointInScreenToAppWindow(point);
+ newX = (int) (point.x + 0.5);
+ newY = (int) (point.y + 0.5);
+ }
+ Message msg = mHandler.obtainMessage(MSG_WINDOW_MOVED, newX, newY);
mHandler.sendMessage(msg);
}
@@ -4615,9 +4737,19 @@ public final class ViewRootImpl implements ViewParent,
// ViewAncestor never intercepts touch event, so this can be a no-op
}
- public boolean requestChildRectangleOnScreen(View child, Rect rectangle,
- boolean immediate) {
- return scrollToRectOrFocus(rectangle, immediate);
+ public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
+ final boolean scrolled = scrollToRectOrFocus(rectangle, immediate);
+ if (rectangle != null) {
+ mTempRect.set(rectangle);
+ mTempRect.offset(0, -mCurScrollY);
+ mTempRect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
+ try {
+ mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect, immediate);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+ return scrolled;
}
public void childHasTransientStateChanged(View child, boolean hasTransientState) {
@@ -4664,41 +4796,48 @@ public final class ViewRootImpl implements ViewParent,
}
}
- static class InputMethodCallback extends IInputMethodCallback.Stub {
+ static final class InputMethodCallback implements InputMethodManager.FinishedEventCallback {
private WeakReference<ViewRootImpl> mViewAncestor;
public InputMethodCallback(ViewRootImpl viewAncestor) {
mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
}
+ @Override
public void finishedEvent(int seq, boolean handled) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchImeFinishedEvent(seq, handled);
}
}
-
- public void sessionCreated(IInputMethodSession session) {
- // Stub -- not for use in the client.
- }
}
static class W extends IWindow.Stub {
private final WeakReference<ViewRootImpl> mViewAncestor;
+ private final IWindowSession mWindowSession;
W(ViewRootImpl viewAncestor) {
mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
+ mWindowSession = viewAncestor.mWindowSession;
}
- public void resized(int w, int h, Rect contentInsets,
+ public void resized(Rect frame, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
- viewAncestor.dispatchResized(w, h, contentInsets,
+ viewAncestor.dispatchResized(frame, contentInsets,
visibleInsets, reportDraw, newConfig);
}
}
+ @Override
+ public void moved(int newX, int newY) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchMoved(newX, newY);
+ }
+ }
+
public void dispatchAppVisibility(boolean visible) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
@@ -4778,7 +4917,7 @@ public final class ViewRootImpl implements ViewParent,
boolean sync) {
if (sync) {
try {
- sWindowSession.wallpaperOffsetsComplete(asBinder());
+ mWindowSession.wallpaperOffsetsComplete(asBinder());
} catch (RemoteException e) {
}
}
@@ -4788,7 +4927,7 @@ public final class ViewRootImpl implements ViewParent,
int z, Bundle extras, boolean sync) {
if (sync) {
try {
- sWindowSession.wallpaperCommandComplete(asBinder(), null);
+ mWindowSession.wallpaperCommandComplete(asBinder(), null);
} catch (RemoteException e) {
}
}
@@ -5186,15 +5325,13 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
- int windowLeft, int windowTop, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
- windowLeft, windowTop, interactionId, callback, flags, interrogatingPid,
- interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5227,15 +5364,13 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId,
- int windowLeft, int windowTop, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId,
- windowLeft, windowTop, interactionId, callback, flags, interrogatingPid,
- interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5248,15 +5383,13 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
- int windowLeft, int windowTop, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text,
- windowLeft, windowTop, interactionId, callback, flags, interrogatingPid,
- interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5268,15 +5401,14 @@ public final class ViewRootImpl implements ViewParent,
}
@Override
- public void findFocus(long accessibilityNodeId, int focusType, int windowLeft,
- int windowTop, int interactionId,
+ public void findFocus(long accessibilityNodeId, int focusType, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
- .findFocusClientThread(accessibilityNodeId, focusType, windowLeft, windowTop,
- interactionId, callback, flags, interrogatingPid, interrogatingTid);
+ .findFocusClientThread(accessibilityNodeId, focusType, interactionId, callback,
+ flags, interrogatingPid, interrogatingTid);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5288,15 +5420,14 @@ public final class ViewRootImpl implements ViewParent,
}
@Override
- public void focusSearch(long accessibilityNodeId, int direction, int windowLeft,
- int windowTop, int interactionId,
+ public void focusSearch(long accessibilityNodeId, int direction, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
- .focusSearchClientThread(accessibilityNodeId, direction, windowLeft, windowTop,
- interactionId, callback, flags, interrogatingPid, interrogatingTid);
+ .focusSearchClientThread(accessibilityNodeId, direction, interactionId,
+ callback, flags, interrogatingPid, interrogatingTid);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 1c5d436..cfcf3c0 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -32,12 +32,18 @@ import java.util.concurrent.CopyOnWriteArrayList;
* for more information.
*/
public final class ViewTreeObserver {
+ // Recursive listeners use CopyOnWriteArrayList
private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
- private CopyOnWriteArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
- private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
- private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners;
- private ArrayList<OnPreDrawListener> mOnPreDrawListeners;
+
+ // Non-recursive listeners use CopyOnWriteArray
+ // Any listener invoked from ViewRootImpl.performTraversals() should not be recursive
+ private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
+ private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
+ private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
+ private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
+
+ // These listeners cannot be mutated during dispatch
private ArrayList<OnDrawListener> mOnDrawListeners;
private boolean mAlive = true;
@@ -147,7 +153,7 @@ public final class ViewTreeObserver {
* windows behind it should be placed.
*/
public final Rect contentInsets = new Rect();
-
+
/**
* Offsets from the frame of the window at which windows behind it
* are visible.
@@ -166,13 +172,13 @@ public final class ViewTreeObserver {
* can be touched.
*/
public static final int TOUCHABLE_INSETS_FRAME = 0;
-
+
/**
* Option for {@link #setTouchableInsets(int)}: the area inside of
* the content insets can be touched.
*/
public static final int TOUCHABLE_INSETS_CONTENT = 1;
-
+
/**
* Option for {@link #setTouchableInsets(int)}: the area inside of
* the visible insets can be touched.
@@ -195,7 +201,7 @@ public final class ViewTreeObserver {
}
int mTouchableInsets;
-
+
void reset() {
contentInsets.setEmpty();
visibleInsets.setEmpty();
@@ -231,7 +237,7 @@ public final class ViewTreeObserver {
mTouchableInsets = other.mTouchableInsets;
}
}
-
+
/**
* Interface definition for a callback to be invoked when layout has
* completed and the client can compute its interior insets.
@@ -363,7 +369,7 @@ public final class ViewTreeObserver {
checkIsAlive();
if (mOnGlobalLayoutListeners == null) {
- mOnGlobalLayoutListeners = new CopyOnWriteArrayList<OnGlobalLayoutListener>();
+ mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
}
mOnGlobalLayoutListeners.add(listener);
@@ -413,7 +419,7 @@ public final class ViewTreeObserver {
checkIsAlive();
if (mOnPreDrawListeners == null) {
- mOnPreDrawListeners = new ArrayList<OnPreDrawListener>();
+ mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
}
mOnPreDrawListeners.add(listener);
@@ -485,7 +491,7 @@ public final class ViewTreeObserver {
checkIsAlive();
if (mOnScrollChangedListeners == null) {
- mOnScrollChangedListeners = new CopyOnWriteArrayList<OnScrollChangedListener>();
+ mOnScrollChangedListeners = new CopyOnWriteArray<OnScrollChangedListener>();
}
mOnScrollChangedListeners.add(listener);
@@ -558,7 +564,7 @@ public final class ViewTreeObserver {
if (mOnComputeInternalInsetsListeners == null) {
mOnComputeInternalInsetsListeners =
- new CopyOnWriteArrayList<OnComputeInternalInsetsListener>();
+ new CopyOnWriteArray<OnComputeInternalInsetsListener>();
}
mOnComputeInternalInsetsListeners.add(listener);
@@ -640,10 +646,16 @@ public final class ViewTreeObserver {
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
- final CopyOnWriteArrayList<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
+ final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
if (listeners != null && listeners.size() > 0) {
- for (OnGlobalLayoutListener listener : listeners) {
- listener.onGlobalLayout();
+ CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
+ try {
+ int count = access.size();
+ for (int i = 0; i < count; i++) {
+ access.get(i).onGlobalLayout();
+ }
+ } finally {
+ listeners.end();
}
}
}
@@ -658,17 +670,17 @@ public final class ViewTreeObserver {
*/
@SuppressWarnings("unchecked")
public final boolean dispatchOnPreDraw() {
- // NOTE: we *must* clone the listener list to perform the dispatching.
- // The clone is a safe guard against listeners that
- // could mutate the list by calling the various add/remove methods. This prevents
- // the array from being modified while we process it.
boolean cancelDraw = false;
- if (mOnPreDrawListeners != null && mOnPreDrawListeners.size() > 0) {
- final ArrayList<OnPreDrawListener> listeners =
- (ArrayList<OnPreDrawListener>) mOnPreDrawListeners.clone();
- int numListeners = listeners.size();
- for (int i = 0; i < numListeners; ++i) {
- cancelDraw |= !(listeners.get(i).onPreDraw());
+ final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
+ if (listeners != null && listeners.size() > 0) {
+ CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
+ try {
+ int count = access.size();
+ for (int i = 0; i < count; i++) {
+ cancelDraw |= !(access.get(i).onPreDraw());
+ }
+ } finally {
+ listeners.end();
}
}
return cancelDraw;
@@ -710,10 +722,16 @@ public final class ViewTreeObserver {
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
- final CopyOnWriteArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
+ final CopyOnWriteArray<OnScrollChangedListener> listeners = mOnScrollChangedListeners;
if (listeners != null && listeners.size() > 0) {
- for (OnScrollChangedListener listener : listeners) {
- listener.onScrollChanged();
+ CopyOnWriteArray.Access<OnScrollChangedListener> access = listeners.start();
+ try {
+ int count = access.size();
+ for (int i = 0; i < count; i++) {
+ access.get(i).onScrollChanged();
+ }
+ } finally {
+ listeners.end();
}
}
}
@@ -722,11 +740,11 @@ public final class ViewTreeObserver {
* Returns whether there are listeners for computing internal insets.
*/
final boolean hasComputeInternalInsetsListeners() {
- final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners =
+ final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
mOnComputeInternalInsetsListeners;
return (listeners != null && listeners.size() > 0);
}
-
+
/**
* Calls all listeners to compute the current insets.
*/
@@ -735,12 +753,105 @@ public final class ViewTreeObserver {
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
- final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners =
+ final CopyOnWriteArray<OnComputeInternalInsetsListener> listeners =
mOnComputeInternalInsetsListeners;
if (listeners != null && listeners.size() > 0) {
- for (OnComputeInternalInsetsListener listener : listeners) {
- listener.onComputeInternalInsets(inoutInfo);
+ CopyOnWriteArray.Access<OnComputeInternalInsetsListener> access = listeners.start();
+ try {
+ int count = access.size();
+ for (int i = 0; i < count; i++) {
+ access.get(i).onComputeInternalInsets(inoutInfo);
+ }
+ } finally {
+ listeners.end();
+ }
+ }
+ }
+
+ /**
+ * Copy on write array. This array is not thread safe, and only one loop can
+ * iterate over this array at any given time. This class avoids allocations
+ * until a concurrent modification happens.
+ *
+ * Usage:
+ *
+ * CopyOnWriteArray.Access<MyData> access = array.start();
+ * try {
+ * for (int i = 0; i < access.size(); i++) {
+ * MyData d = access.get(i);
+ * }
+ * } finally {
+ * access.end();
+ * }
+ */
+ static class CopyOnWriteArray<T> {
+ private ArrayList<T> mData = new ArrayList<T>();
+ private ArrayList<T> mDataCopy;
+
+ private final Access<T> mAccess = new Access<T>();
+
+ private boolean mStart;
+
+ static class Access<T> {
+ private ArrayList<T> mData;
+ private int mSize;
+
+ T get(int index) {
+ return mData.get(index);
+ }
+
+ int size() {
+ return mSize;
+ }
+ }
+
+ CopyOnWriteArray() {
+ }
+
+ private ArrayList<T> getArray() {
+ if (mStart) {
+ if (mDataCopy == null) mDataCopy = new ArrayList<T>(mData);
+ return mDataCopy;
}
+ return mData;
+ }
+
+ Access<T> start() {
+ if (mStart) throw new IllegalStateException("Iteration already started");
+ mStart = true;
+ mDataCopy = null;
+ mAccess.mData = mData;
+ mAccess.mSize = mData.size();
+ return mAccess;
+ }
+
+ void end() {
+ if (!mStart) throw new IllegalStateException("Iteration not started");
+ mStart = false;
+ if (mDataCopy != null) {
+ mData = mDataCopy;
+ }
+ mDataCopy = null;
+ }
+
+ int size() {
+ return getArray().size();
+ }
+
+ void add(T item) {
+ getArray().add(item);
+ }
+
+ void addAll(CopyOnWriteArray<T> array) {
+ getArray().addAll(array.mData);
+ }
+
+ void remove(T item) {
+ getArray().remove(item);
+ }
+
+ void clear() {
+ getArray().clear();
}
}
}
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index cf9bcdd..d7c7f46 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -18,6 +18,7 @@ package android.view;
import com.android.internal.R;
+import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface.OnDismissListener;
import android.content.BroadcastReceiver;
@@ -92,6 +93,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
private static final int MSG_REMOTE_VOLUME_CHANGED = 8;
private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9;
private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10;
+ private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11;
// Pseudo stream type for master volume
private static final int STREAM_MASTER = -100;
@@ -104,6 +106,9 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
private boolean mShowCombinedVolumes;
private boolean mVoiceCapable;
+ // True if we want to play tones on the system stream when the master stream is specified.
+ private final boolean mPlayMasterStreamTones;
+
/** Dialog containing all the sliders */
private final Dialog mDialog;
/** Dialog's content view */
@@ -208,6 +213,38 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
private ToneGenerator mToneGenerators[];
private Vibrator mVibrator;
+ private static AlertDialog sConfirmSafeVolumeDialog;
+ private static Object sConfirmSafeVolumeLock = new Object();
+
+ private static class WarningDialogReceiver extends BroadcastReceiver
+ implements DialogInterface.OnDismissListener {
+ private Context mContext;
+ private Dialog mDialog;
+
+ WarningDialogReceiver(Context context, Dialog dialog) {
+ mContext = context;
+ mDialog = dialog;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ context.registerReceiver(this, filter);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mDialog.cancel();
+ synchronized (sConfirmSafeVolumeLock) {
+ sConfirmSafeVolumeDialog = null;
+ }
+ }
+
+ public void onDismiss(DialogInterface unused) {
+ mContext.unregisterReceiver(this);
+ synchronized (sConfirmSafeVolumeLock) {
+ sConfirmSafeVolumeDialog = null;
+ }
+ }
+ }
+
+
public VolumePanel(final Context context, AudioService volumeService) {
mContext = context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -282,6 +319,13 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
mMoreButton.setOnClickListener(this);
}
+ boolean masterVolumeOnly = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_useMasterVolume);
+ boolean masterVolumeKeySounds = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_useVolumeKeySounds);
+
+ mPlayMasterStreamTones = masterVolumeOnly && masterVolumeKeySounds;
+
listenToRingerMode();
}
@@ -518,6 +562,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
postMuteChanged(STREAM_MASTER, flags);
}
+ public void postDisplaySafeVolumeWarning() {
+ if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return;
+ obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, 0, 0).sendToTarget();
+ }
+
/**
* Override this if you have other work to do when the volume changes (for
* example, vibrating, playing a sound, etc.). Make sure to call through to
@@ -650,9 +699,12 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
if (sc.seekbarView.getMax() != max) {
sc.seekbarView.setMax(max);
}
+
sc.seekbarView.setProgress(index);
- if (streamType != mAudioManager.getMasterStreamType()
- && streamType != AudioService.STREAM_REMOTE_MUSIC && isMuted(streamType)) {
+ if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) ||
+ (streamType != mAudioManager.getMasterStreamType() &&
+ streamType != AudioService.STREAM_REMOTE_MUSIC &&
+ isMuted(streamType))) {
sc.seekbarView.setEnabled(false);
} else {
sc.seekbarView.setEnabled(true);
@@ -786,11 +838,46 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
}
}
+ protected void onDisplaySafeVolumeWarning() {
+ synchronized (sConfirmSafeVolumeLock) {
+ if (sConfirmSafeVolumeDialog != null) {
+ return;
+ }
+ sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext)
+ .setMessage(com.android.internal.R.string.safe_media_volume_warning)
+ .setPositiveButton(com.android.internal.R.string.yes,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ mAudioService.disableSafeMediaVolume();
+ }
+ })
+ .setNegativeButton(com.android.internal.R.string.no, null)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .create();
+ final WarningDialogReceiver warning = new WarningDialogReceiver(mContext,
+ sConfirmSafeVolumeDialog);
+
+ sConfirmSafeVolumeDialog.setOnDismissListener(warning);
+ sConfirmSafeVolumeDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ sConfirmSafeVolumeDialog.show();
+ }
+ }
+
/**
* Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
*/
private ToneGenerator getOrCreateToneGenerator(int streamType) {
- if (streamType == STREAM_MASTER) return null;
+ if (streamType == STREAM_MASTER) {
+ // For devices that use the master volume setting only but still want to
+ // play a volume-changed tone, direct the master volume pseudostream to
+ // the system stream's tone generator.
+ if (mPlayMasterStreamTones) {
+ streamType = AudioManager.STREAM_SYSTEM;
+ } else {
+ return null;
+ }
+ }
synchronized (this) {
if (mToneGenerators[streamType] == null) {
try {
@@ -891,6 +978,10 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
case MSG_SLIDER_VISIBILITY_CHANGED:
onSliderVisibilityChanged(msg.arg1, msg.arg2);
break;
+
+ case MSG_DISPLAY_SAFE_VOLUME_WARNING:
+ onDisplaySafeVolumeWarning();
+ break;
}
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index b0e90db..06974d3 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -16,9 +16,7 @@
package android.view;
-import android.app.Application;
import android.content.Context;
-import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
@@ -27,7 +25,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemProperties;
-import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;
/**
@@ -92,6 +89,13 @@ public abstract class Window {
* If overlay is enabled, the action mode UI will be allowed to cover existing window content.
*/
public static final int FEATURE_ACTION_MODE_OVERLAY = 10;
+
+ /**
+ * Max value used as a feature ID
+ * @hide
+ */
+ public static final int FEATURE_MAX = FEATURE_ACTION_MODE_OVERLAY;
+
/** Flag for setting the progress bar's visibility to VISIBLE */
public static final int PROGRESS_VISIBILITY_ON = -1;
/** Flag for setting the progress bar's visibility to GONE */
@@ -119,6 +123,8 @@ public abstract class Window {
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
+ private static final String PROPERTY_HARDWARE_UI = "persist.sys.ui.hw";
+
private final Context mContext;
private TypedArray mWindowStyle;
@@ -126,6 +132,7 @@ public abstract class Window {
private WindowManager mWindowManager;
private IBinder mAppToken;
private String mAppName;
+ private boolean mHardwareAccelerated;
private Window mContainer;
private Window mActiveChild;
private boolean mIsActive = false;
@@ -454,7 +461,7 @@ public abstract class Window {
* display panels. This is <em>not</em> used for displaying the
* Window itself -- that must be done by the client.
*
- * @param wm The ViewManager for adding new windows.
+ * @param wm The window manager for adding new windows.
*/
public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
setWindowManager(wm, appToken, appName, false);
@@ -465,86 +472,64 @@ public abstract class Window {
* display panels. This is <em>not</em> used for displaying the
* Window itself -- that must be done by the client.
*
- * @param wm The ViewManager for adding new windows.
+ * @param wm The window manager for adding new windows.
*/
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
+ mHardwareAccelerated = hardwareAccelerated
+ || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
- wm = WindowManagerImpl.getDefault();
+ wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
- mWindowManager = new LocalWindowManager(wm, hardwareAccelerated);
+ mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
- static CompatibilityInfoHolder getCompatInfo(Context context) {
- Application app = (Application)context.getApplicationContext();
- return app != null ? app.mLoadedApk.mCompatibilityInfo : new CompatibilityInfoHolder();
- }
-
- private class LocalWindowManager extends WindowManagerImpl.CompatModeWrapper {
- private static final String PROPERTY_HARDWARE_UI = "persist.sys.ui.hw";
-
- private final boolean mHardwareAccelerated;
-
- LocalWindowManager(WindowManager wm, boolean hardwareAccelerated) {
- super(wm, getCompatInfo(mContext));
- mHardwareAccelerated = hardwareAccelerated ||
- SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
- }
-
- public boolean isHardwareAccelerated() {
- return mHardwareAccelerated;
- }
-
- public final void addView(View view, ViewGroup.LayoutParams params) {
- // Let this throw an exception on a bad params.
- WindowManager.LayoutParams wp = (WindowManager.LayoutParams)params;
- CharSequence curTitle = wp.getTitle();
- if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
- wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
- if (wp.token == null) {
- View decor = peekDecorView();
- if (decor != null) {
- wp.token = decor.getWindowToken();
- }
+ void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
+ CharSequence curTitle = wp.getTitle();
+ if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
+ wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+ if (wp.token == null) {
+ View decor = peekDecorView();
+ if (decor != null) {
+ wp.token = decor.getWindowToken();
}
- if (curTitle == null || curTitle.length() == 0) {
- String title;
- if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) {
- title="Media";
- } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) {
- title="MediaOvr";
- } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
- title="Panel";
- } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) {
- title="SubPanel";
- } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) {
- title="AtchDlg";
- } else {
- title=Integer.toString(wp.type);
- }
- if (mAppName != null) {
- title += ":" + mAppName;
- }
- wp.setTitle(title);
- }
- } else {
- if (wp.token == null) {
- wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
+ }
+ if (curTitle == null || curTitle.length() == 0) {
+ String title;
+ if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) {
+ title="Media";
+ } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) {
+ title="MediaOvr";
+ } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
+ title="Panel";
+ } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) {
+ title="SubPanel";
+ } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) {
+ title="AtchDlg";
+ } else {
+ title=Integer.toString(wp.type);
}
- if ((curTitle == null || curTitle.length() == 0)
- && mAppName != null) {
- wp.setTitle(mAppName);
+ if (mAppName != null) {
+ title += ":" + mAppName;
}
- }
- if (wp.packageName == null) {
- wp.packageName = mContext.getPackageName();
+ wp.setTitle(title);
}
- if (mHardwareAccelerated) {
- wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ } else {
+ if (wp.token == null) {
+ wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
+ }
+ if ((curTitle == null || curTitle.length() == 0)
+ && mAppName != null) {
+ wp.setTitle(mAppName);
}
- super.addView(view, params);
+ }
+ if (wp.packageName == null) {
+ wp.packageName = mContext.getPackageName();
+ }
+ if (mHardwareAccelerated) {
+ wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
@@ -718,6 +703,7 @@ public abstract class Window {
* per {@link #setFlags}.
* @param flags The flag bits to be set.
* @see #setFlags
+ * @see #clearFlags
*/
public void addFlags(int flags) {
setFlags(flags, flags);
@@ -728,6 +714,7 @@ public abstract class Window {
* per {@link #setFlags}.
* @param flags The flag bits to be cleared.
* @see #setFlags
+ * @see #addFlags
*/
public void clearFlags(int flags) {
setFlags(0, flags);
@@ -749,6 +736,8 @@ public abstract class Window {
*
* @param flags The new window flags (see WindowManager.LayoutParams).
* @param mask Which of the window flag bits to modify.
+ * @see #addFlags
+ * @see #clearFlags
*/
public void setFlags(int flags, int mask) {
final WindowManager.LayoutParams attrs = getAttributes();
diff --git a/core/java/android/view/WindowInfo.aidl b/core/java/android/view/WindowInfo.aidl
new file mode 100644
index 0000000..23e927a
--- /dev/null
+++ b/core/java/android/view/WindowInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 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.view;
+
+parcelable WindowInfo;
diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java
new file mode 100644
index 0000000..7d16e14
--- /dev/null
+++ b/core/java/android/view/WindowInfo.java
@@ -0,0 +1,175 @@
+/*
+ * 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.view;
+
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information the state of a window.
+ *
+ * @hide
+ */
+public class WindowInfo implements Parcelable {
+
+ private static final int MAX_POOL_SIZE = 20;
+
+ private static int UNDEFINED = -1;
+
+ private static Object sPoolLock = new Object();
+ private static WindowInfo sPool;
+ private static int sPoolSize;
+
+ private WindowInfo mNext;
+ private boolean mInPool;
+
+ public IBinder token;
+
+ public final Rect frame = new Rect();
+
+ public final Rect touchableRegion = new Rect();
+
+ public int type = UNDEFINED;
+
+ public float compatibilityScale = UNDEFINED;
+
+ public boolean visible;
+
+ public int displayId = UNDEFINED;
+
+ public int layer = UNDEFINED;
+
+ private WindowInfo() {
+ /* do nothing - reduce visibility */
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeStrongBinder(token);
+ parcel.writeParcelable(frame, 0);
+ parcel.writeParcelable(touchableRegion, 0);
+ parcel.writeInt(type);
+ parcel.writeFloat(compatibilityScale);
+ parcel.writeInt(visible ? 1 : 0);
+ parcel.writeInt(displayId);
+ parcel.writeInt(layer);
+ recycle();
+ }
+
+ private void initFromParcel(Parcel parcel) {
+ token = parcel.readStrongBinder();
+ frame.set((Rect) parcel.readParcelable(null));
+ touchableRegion.set((Rect) parcel.readParcelable(null));
+ type = parcel.readInt();
+ compatibilityScale = parcel.readFloat();
+ visible = (parcel.readInt() == 1);
+ displayId = parcel.readInt();
+ layer = parcel.readInt();
+ }
+
+ public static WindowInfo obtain(WindowInfo other) {
+ WindowInfo info = obtain();
+ info.token = other.token;
+ info.frame.set(other.frame);
+ info.touchableRegion.set(other.touchableRegion);
+ info.type = other.type;
+ info.compatibilityScale = other.compatibilityScale;
+ info.visible = other.visible;
+ info.displayId = other.displayId;
+ info.layer = other.layer;
+ return info;
+ }
+
+ public static WindowInfo obtain() {
+ synchronized (sPoolLock) {
+ if (sPoolSize > 0) {
+ WindowInfo info = sPool;
+ sPool = info.mNext;
+ info.mNext = null;
+ info.mInPool = false;
+ sPoolSize--;
+ return info;
+ } else {
+ return new WindowInfo();
+ }
+ }
+ }
+
+ public void recycle() {
+ if (mInPool) {
+ throw new IllegalStateException("Already recycled.");
+ }
+ clear();
+ synchronized (sPoolLock) {
+ if (sPoolSize < MAX_POOL_SIZE) {
+ mNext = sPool;
+ sPool = this;
+ mInPool = true;
+ sPoolSize++;
+ }
+ }
+ }
+
+ private void clear() {
+ token = null;
+ frame.setEmpty();
+ touchableRegion.setEmpty();
+ type = UNDEFINED;
+ compatibilityScale = UNDEFINED;
+ visible = false;
+ displayId = UNDEFINED;
+ layer = UNDEFINED;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Window [token:").append((token != null) ? token.hashCode() : null);
+ builder.append(", displayId:").append(displayId);
+ builder.append(", type:").append(type);
+ builder.append(", visible:").append(visible);
+ builder.append(", layer:").append(layer);
+ builder.append(", compatibilityScale:").append(compatibilityScale);
+ builder.append(", frame:").append(frame);
+ builder.append(", touchableRegion:").append(touchableRegion);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator<WindowInfo> CREATOR =
+ new Parcelable.Creator<WindowInfo>() {
+ public WindowInfo createFromParcel(Parcel parcel) {
+ WindowInfo info = WindowInfo.obtain();
+ info.initFromParcel(parcel);
+ return info;
+ }
+
+ public WindowInfo[] newArray(int size) {
+ return new WindowInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d94275b..6e51270 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -16,6 +16,8 @@
package android.view;
+import android.app.Presentation;
+import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.os.IBinder;
@@ -29,6 +31,17 @@ import android.util.Log;
* The interface that apps use to talk to the window manager.
* <p>
* Use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code> to get one of these.
+ * </p><p>
+ * Each window manager instance is bound to a particular {@link Display}.
+ * To obtain a {@link WindowManager} for a different display, use
+ * {@link Context#createDisplayContext} to obtain a {@link Context} for that
+ * display, then use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code>
+ * to get the WindowManager.
+ * </p><p>
+ * The simplest way to show a window on another display is to create a
+ * {@link Presentation}. The presentation will automatically obtain a
+ * {@link WindowManager} and {@link Context} for that display.
+ * </p>
*
* @see android.content.Context#getSystemService
* @see android.content.Context#WINDOW_SERVICE
@@ -49,12 +62,24 @@ public interface WindowManager extends ViewManager {
}
/**
- * Use this method to get the default Display object.
- *
- * @return default Display object
+ * Returns the {@link Display} upon which this {@link WindowManager} instance
+ * will create new windows.
+ * <p>
+ * Despite the name of this method, the display that is returned is not
+ * necessarily the primary display of the system (see {@link Display#DEFAULT_DISPLAY}).
+ * The returned display could instead be a secondary display that this
+ * window manager instance is managing. Think of it as the display that
+ * this {@link WindowManager} instance uses by default.
+ * </p><p>
+ * To create windows on a different display, you need to obtain a
+ * {@link WindowManager} for that {@link Display}. (See the {@link WindowManager}
+ * class documentation for more information.)
+ * </p>
+ *
+ * @return The display that this window manager is managing.
*/
public Display getDefaultDisplay();
-
+
/**
* Special variation of {@link #removeView} that immediately invokes
* the given view hierarchy's {@link View#onDetachedFromWindow()
@@ -64,15 +89,7 @@ public interface WindowManager extends ViewManager {
* @param view The view to be removed.
*/
public void removeViewImmediate(View view);
-
- /**
- * Return true if this window manager is configured to request hardware
- * accelerated windows. This does <em>not</em> guarantee that they will
- * actually be accelerated, since that depends on the device supporting them.
- * @hide
- */
- public boolean isHardwareAccelerated();
-
+
public static class LayoutParams extends ViewGroup.LayoutParams
implements Parcelable {
/**
@@ -162,6 +179,7 @@ public interface WindowManager extends ViewManager {
@ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA, to = "TYPE_APPLICATION_MEDIA"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_SUB_PANEL, to = "TYPE_APPLICATION_SUB_PANEL"),
@ViewDebug.IntToString(from = TYPE_APPLICATION_ATTACHED_DIALOG, to = "TYPE_APPLICATION_ATTACHED_DIALOG"),
+ @ViewDebug.IntToString(from = TYPE_APPLICATION_MEDIA_OVERLAY, to = "TYPE_APPLICATION_MEDIA_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_STATUS_BAR, to = "TYPE_STATUS_BAR"),
@ViewDebug.IntToString(from = TYPE_SEARCH_BAR, to = "TYPE_SEARCH_BAR"),
@ViewDebug.IntToString(from = TYPE_PHONE, to = "TYPE_PHONE"),
@@ -170,8 +188,6 @@ public interface WindowManager extends ViewManager {
@ViewDebug.IntToString(from = TYPE_TOAST, to = "TYPE_TOAST"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_OVERLAY, to = "TYPE_SYSTEM_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_PRIORITY_PHONE, to = "TYPE_PRIORITY_PHONE"),
- @ViewDebug.IntToString(from = TYPE_STATUS_BAR_PANEL, to = "TYPE_STATUS_BAR_PANEL"),
- @ViewDebug.IntToString(from = TYPE_STATUS_BAR_SUB_PANEL, to = "TYPE_STATUS_BAR_SUB_PANEL"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_DIALOG, to = "TYPE_SYSTEM_DIALOG"),
@ViewDebug.IntToString(from = TYPE_KEYGUARD_DIALOG, to = "TYPE_KEYGUARD_DIALOG"),
@ViewDebug.IntToString(from = TYPE_SYSTEM_ERROR, to = "TYPE_SYSTEM_ERROR"),
@@ -185,7 +201,12 @@ public interface WindowManager extends ViewManager {
@ViewDebug.IntToString(from = TYPE_POINTER, to = "TYPE_POINTER"),
@ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR, to = "TYPE_NAVIGATION_BAR"),
@ViewDebug.IntToString(from = TYPE_VOLUME_OVERLAY, to = "TYPE_VOLUME_OVERLAY"),
- @ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS, to = "TYPE_BOOT_PROGRESS")
+ @ViewDebug.IntToString(from = TYPE_BOOT_PROGRESS, to = "TYPE_BOOT_PROGRESS"),
+ @ViewDebug.IntToString(from = TYPE_HIDDEN_NAV_CONSUMER, to = "TYPE_HIDDEN_NAV_CONSUMER"),
+ @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")
})
public int type;
@@ -435,6 +456,25 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
/**
+ * Window type: Behind the universe of the real windows.
+ * @hide
+ */
+ public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25;
+
+ /**
+ * Window type: Display overlay window. Used to simulate secondary display devices.
+ * @hide
+ */
+ public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
+
+ /**
+ * Window type: Magnification overlay window. Used to highlight the magnified
+ * portion of a display when accessibility magnification is enabled.
+ * @hide
+ */
+ public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
+
+ /**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
@@ -530,7 +570,9 @@ public interface WindowManager extends ViewManager {
public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
/** Window flag: turn on dithering when compositing this window to
- * the screen. */
+ * the screen.
+ * @deprecated This flag is no longer used. */
+ @Deprecated
public static final int FLAG_DITHER = 0x00001000;
/** Window flag: don't allow screen shots while this window is
@@ -718,7 +760,6 @@ public interface WindowManager extends ViewManager {
* @see #FLAG_LAYOUT_NO_LIMITS
* @see #FLAG_FULLSCREEN
* @see #FLAG_FORCE_NOT_FULLSCREEN
- * @see #FLAG_DITHER
* @see #FLAG_SECURE
* @see #FLAG_SCALED
* @see #FLAG_IGNORE_CHEEK_PRESSES
@@ -1135,14 +1176,42 @@ public interface WindowManager extends ViewManager {
public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002;
/**
+ * When this window has focus, does not call user activity for all input events so
+ * the application will have to do it itself. Should only be used by
+ * the keyguard and phone app.
+ * <p>
+ * Should only be used by the keyguard and phone app.
+ * </p>
+ *
+ * @hide
+ */
+ public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004;
+
+ /**
* Control special features of the input subsystem.
*
* @see #INPUT_FEATURE_DISABLE_TOUCH_PAD_GESTURES
* @see #INPUT_FEATURE_NO_INPUT_CHANNEL
+ * @see #INPUT_FEATURE_DISABLE_USER_ACTIVITY
* @hide
*/
public int inputFeatures;
+ /**
+ * Sets the number of milliseconds before the user activity timeout occurs
+ * when this window has focus. A value of -1 uses the standard timeout.
+ * A value of 0 uses the minimum support display timeout.
+ * <p>
+ * This property can only be used to reduce the user specified display timeout;
+ * it can never make the timeout longer than it normally would be.
+ * </p><p>
+ * Should only be used by the keyguard and phone app.
+ * </p>
+ *
+ * @hide
+ */
+ public long userActivityTimeout = -1;
+
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
@@ -1227,6 +1296,7 @@ public interface WindowManager extends ViewManager {
out.writeInt(subtreeSystemUiVisibility);
out.writeInt(hasSystemUiListeners ? 1 : 0);
out.writeInt(inputFeatures);
+ out.writeLong(userActivityTimeout);
}
public static final Parcelable.Creator<LayoutParams> CREATOR
@@ -1267,6 +1337,7 @@ public interface WindowManager extends ViewManager {
subtreeSystemUiVisibility = in.readInt();
hasSystemUiListeners = in.readInt() != 0;
inputFeatures = in.readInt();
+ userActivityTimeout = in.readLong();
}
@SuppressWarnings({"PointlessBitwiseExpression"})
@@ -1293,6 +1364,8 @@ public interface WindowManager extends ViewManager {
/** {@hide} */
public static final int PRIVATE_FLAGS_CHANGED = 1<<16;
/** {@hide} */
+ public static final int USER_ACTIVITY_TIMEOUT_CHANGED = 1<<17;
+ /** {@hide} */
public static final int EVERYTHING_CHANGED = 0xffffffff;
// internal buffer to backup/restore parameters under compatibility mode.
@@ -1414,6 +1487,11 @@ public interface WindowManager extends ViewManager {
changes |= INPUT_FEATURES_CHANGED;
}
+ if (userActivityTimeout != o.userActivityTimeout) {
+ userActivityTimeout = o.userActivityTimeout;
+ changes |= USER_ACTIVITY_TIMEOUT_CHANGED;
+ }
+
return changes;
}
@@ -1506,6 +1584,9 @@ public interface WindowManager extends ViewManager {
if (inputFeatures != 0) {
sb.append(" if=0x").append(Integer.toHexString(inputFeatures));
}
+ if (userActivityTimeout >= 0) {
+ sb.append(" userActivityTimeout=").append(userActivityTimeout);
+ }
sb.append('}');
return sb.toString();
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
new file mode 100644
index 0000000..7855763c
--- /dev/null
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -0,0 +1,516 @@
+/*
+ * 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.view;
+
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+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.Log;
+import android.view.inputmethod.InputMethodManager;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+
+/**
+ * Provides low-level communication with the system window manager for
+ * operations that are not associated with any particular context.
+ *
+ * This class is only used internally to implement global functions where
+ * the caller already knows the display and relevant compatibility information
+ * for the operation. For most purposes, you should use {@link WindowManager} instead
+ * since it is bound to a context.
+ *
+ * @see WindowManagerImpl
+ * @hide
+ */
+public final class WindowManagerGlobal {
+ private static final String TAG = "WindowManager";
+
+ /**
+ * The user is navigating with keys (not the touch screen), so
+ * navigational focus should be shown.
+ */
+ public static final int RELAYOUT_RES_IN_TOUCH_MODE = 0x1;
+
+ /**
+ * This is the first time the window is being drawn,
+ * so the client must call drawingFinished() when done
+ */
+ public static final int RELAYOUT_RES_FIRST_TIME = 0x2;
+
+ /**
+ * The window manager has changed the surface from the last call.
+ */
+ public static final int RELAYOUT_RES_SURFACE_CHANGED = 0x4;
+
+ /**
+ * The window manager is currently animating. It will call
+ * IWindow.doneAnimating() when done.
+ */
+ public static final int RELAYOUT_RES_ANIMATING = 0x8;
+
+ /**
+ * Flag for relayout: the client will be later giving
+ * internal insets; as a result, the window will not impact other window
+ * layouts until the insets are given.
+ */
+ public static final int RELAYOUT_INSETS_PENDING = 0x1;
+
+ /**
+ * Flag for relayout: the client may be currently using the current surface,
+ * so if it is to be destroyed as a part of the relayout the destroy must
+ * be deferred until later. The client will call performDeferredDestroy()
+ * when it is okay.
+ */
+ public static final int RELAYOUT_DEFER_SURFACE_DESTROY = 0x2;
+
+ public static final int ADD_FLAG_APP_VISIBLE = 0x2;
+ public static final int ADD_FLAG_IN_TOUCH_MODE = RELAYOUT_RES_IN_TOUCH_MODE;
+
+ public static final int ADD_OKAY = 0;
+ public static final int ADD_BAD_APP_TOKEN = -1;
+ public static final int ADD_BAD_SUBWINDOW_TOKEN = -2;
+ public static final int ADD_NOT_APP_TOKEN = -3;
+ public static final int ADD_APP_EXITING = -4;
+ public static final int ADD_DUPLICATE_ADD = -5;
+ public static final int ADD_STARTING_NOT_NEEDED = -6;
+ public static final int ADD_MULTIPLE_SINGLETON = -7;
+ public static final int ADD_PERMISSION_DENIED = -8;
+
+ private static WindowManagerGlobal sDefaultWindowManager;
+ private static IWindowManager sWindowManagerService;
+ private static IWindowSession sWindowSession;
+
+ private final Object mLock = new Object();
+
+ private View[] mViews;
+ private ViewRootImpl[] mRoots;
+ private WindowManager.LayoutParams[] mParams;
+ private boolean mNeedsEglTerminate;
+
+ private Runnable mSystemPropertyUpdater;
+
+ private WindowManagerGlobal() {
+ }
+
+ public static WindowManagerGlobal getInstance() {
+ synchronized (WindowManagerGlobal.class) {
+ if (sDefaultWindowManager == null) {
+ sDefaultWindowManager = new WindowManagerGlobal();
+ }
+ return sDefaultWindowManager;
+ }
+ }
+
+ public static IWindowManager getWindowManagerService() {
+ synchronized (WindowManagerGlobal.class) {
+ if (sWindowManagerService == null) {
+ sWindowManagerService = IWindowManager.Stub.asInterface(
+ ServiceManager.getService("window"));
+ }
+ return sWindowManagerService;
+ }
+ }
+
+ public static IWindowSession getWindowSession(Looper mainLooper) {
+ synchronized (WindowManagerGlobal.class) {
+ if (sWindowSession == null) {
+ try {
+ InputMethodManager imm = InputMethodManager.getInstance(mainLooper);
+ IWindowManager windowManager = getWindowManagerService();
+ sWindowSession = windowManager.openSession(
+ imm.getClient(), imm.getInputContext());
+ float animatorScale = windowManager.getAnimationScale(2);
+ ValueAnimator.setDurationScale(animatorScale);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to open window session", e);
+ }
+ }
+ return sWindowSession;
+ }
+ }
+
+ public static IWindowSession peekWindowSession() {
+ synchronized (WindowManagerGlobal.class) {
+ return sWindowSession;
+ }
+ }
+
+ public void addView(View view, ViewGroup.LayoutParams params,
+ Display display, Window parentWindow) {
+ if (view == null) {
+ throw new IllegalArgumentException("view must not be null");
+ }
+ if (display == null) {
+ throw new IllegalArgumentException("display must not be null");
+ }
+ if (!(params instanceof WindowManager.LayoutParams)) {
+ throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
+ }
+
+ final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
+ if (parentWindow != null) {
+ parentWindow.adjustLayoutParamsForSubWindow(wparams);
+ }
+
+ ViewRootImpl root;
+ View panelParentView = null;
+
+ synchronized (mLock) {
+ // Start watching for system property changes.
+ if (mSystemPropertyUpdater == null) {
+ mSystemPropertyUpdater = new Runnable() {
+ @Override public void run() {
+ synchronized (mLock) {
+ for (ViewRootImpl root : mRoots) {
+ root.loadSystemProperties();
+ }
+ }
+ }
+ };
+ SystemProperties.addChangeCallback(mSystemPropertyUpdater);
+ }
+
+ int index = findViewLocked(view, false);
+ if (index >= 0) {
+ throw new IllegalStateException("View " + view
+ + " has already been added to the window manager.");
+ }
+
+ // 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<count; i++) {
+ if (mRoots[i].mWindow.asBinder() == wparams.token) {
+ panelParentView = mViews[i];
+ }
+ }
+ }
+
+ root = new ViewRootImpl(view.getContext(), display);
+
+ view.setLayoutParams(wparams);
+
+ if (mViews == null) {
+ index = 1;
+ mViews = new View[1];
+ mRoots = new ViewRootImpl[1];
+ mParams = new WindowManager.LayoutParams[1];
+ } else {
+ index = mViews.length + 1;
+ Object[] old = mViews;
+ mViews = new View[index];
+ System.arraycopy(old, 0, mViews, 0, index-1);
+ old = mRoots;
+ mRoots = new ViewRootImpl[index];
+ System.arraycopy(old, 0, mRoots, 0, index-1);
+ old = mParams;
+ mParams = new WindowManager.LayoutParams[index];
+ System.arraycopy(old, 0, mParams, 0, index-1);
+ }
+ index--;
+
+ mViews[index] = view;
+ mRoots[index] = root;
+ mParams[index] = wparams;
+ }
+
+ // do this last because it fires off messages to start doing things
+ root.setView(view, wparams, panelParentView);
+ }
+
+ public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
+ if (view == null) {
+ throw new IllegalArgumentException("view must not be null");
+ }
+ if (!(params instanceof WindowManager.LayoutParams)) {
+ throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
+ }
+
+ final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
+
+ view.setLayoutParams(wparams);
+
+ synchronized (mLock) {
+ int index = findViewLocked(view, true);
+ ViewRootImpl root = mRoots[index];
+ mParams[index] = wparams;
+ root.setLayoutParams(wparams, false);
+ }
+ }
+
+ public void removeView(View view, boolean immediate) {
+ if (view == null) {
+ throw new IllegalArgumentException("view must not be null");
+ }
+
+ synchronized (mLock) {
+ int index = findViewLocked(view, true);
+ View curView = removeViewLocked(index, immediate);
+ if (curView == view) {
+ return;
+ }
+
+ throw new IllegalStateException("Calling with view " + view
+ + " but the ViewAncestor is attached to " + curView);
+ }
+ }
+
+ public void closeAll(IBinder token, String who, String what) {
+ synchronized (mLock) {
+ if (mViews == null)
+ return;
+
+ int count = mViews.length;
+ //Log.i("foo", "Closing all windows of " + token);
+ for (int i=0; i<count; i++) {
+ //Log.i("foo", "@ " + i + " token " + mParams[i].token
+ // + " view " + mRoots[i].getView());
+ if (token == null || mParams[i].token == token) {
+ ViewRootImpl root = mRoots[i];
+
+ //Log.i("foo", "Force closing " + root);
+ if (who != null) {
+ WindowLeaked leak = new WindowLeaked(
+ what + " " + who + " has leaked window "
+ + root.getView() + " that was originally added here");
+ leak.setStackTrace(root.getLocation().getStackTrace());
+ Log.e(TAG, leak.getMessage(), leak);
+ }
+
+ removeViewLocked(i, false);
+ i--;
+ count--;
+ }
+ }
+ }
+ }
+
+ private View removeViewLocked(int index, boolean immediate) {
+ ViewRootImpl root = mRoots[index];
+ View view = root.getView();
+
+ if (view != null) {
+ InputMethodManager imm = InputMethodManager.getInstance(view.getContext());
+ if (imm != null) {
+ imm.windowDismissed(mViews[index].getWindowToken());
+ }
+ }
+ root.die(immediate);
+
+ final int count = mViews.length;
+
+ // remove it from the list
+ View[] tmpViews = new View[count-1];
+ removeItem(tmpViews, mViews, index);
+ mViews = tmpViews;
+
+ ViewRootImpl[] tmpRoots = new ViewRootImpl[count-1];
+ removeItem(tmpRoots, mRoots, index);
+ mRoots = tmpRoots;
+
+ WindowManager.LayoutParams[] tmpParams
+ = new WindowManager.LayoutParams[count-1];
+ removeItem(tmpParams, mParams, index);
+ mParams = tmpParams;
+
+ if (view != null) {
+ view.assignParent(null);
+ // func doesn't allow null... does it matter if we clear them?
+ //view.setLayoutParams(null);
+ }
+ return view;
+ }
+
+ private static void removeItem(Object[] dst, Object[] src, int index) {
+ if (dst.length > 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);
+ }
+ }
+ }
+
+ private int findViewLocked(View view, boolean required) {
+ synchronized (mLock) {
+ 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");
+ }
+ return -1;
+ }
+ }
+
+ public void startTrimMemory(int level) {
+ if (HardwareRenderer.isAvailable()) {
+ // On low-end gfx devices we trim when memory is moderate;
+ // on high-end devices we do this when low.
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE
+ || (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE
+ && !ActivityManager.isHighEndGfx())) {
+ // 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();
+ }
+ }
+ // Force a full memory flush
+ mNeedsEglTerminate = true;
+ HardwareRenderer.startTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
+ return;
+ }
+
+ HardwareRenderer.startTrimMemory(level);
+ }
+ }
+
+ public void endTrimMemory() {
+ HardwareRenderer.endTrimMemory();
+
+ if (mNeedsEglTerminate) {
+ ManagedEGLContext.doTerminate();
+ mNeedsEglTerminate = false;
+ }
+ }
+
+ public void trimLocalMemory() {
+ synchronized (mLock) {
+ if (mViews == null) return;
+ int count = mViews.length;
+ for (int i = 0; i < count; i++) {
+ mRoots[i].destroyHardwareLayers();
+ }
+ }
+ }
+
+ public void dumpGfxInfo(FileDescriptor fd) {
+ FileOutputStream fout = new FileOutputStream(fd);
+ PrintWriter pw = new PrintWriter(fout);
+ try {
+ synchronized (mLock) {
+ if (mViews != null) {
+ final int count = mViews.length;
+
+ 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);
+
+ 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];
+
+ for (int i = 0; i < count; i++) {
+ ViewRootImpl root = mRoots[i];
+ root.dumpGfxInfo(info);
+
+ 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");
+
+ 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();
+ }
+ }
+
+ private static String getWindowName(ViewRootImpl root) {
+ return root.mWindowAttributes.getTitle() + "/" +
+ root.getClass().getName() + '@' + Integer.toHexString(root.hashCode());
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+}
+
+final class WindowLeaked extends AndroidRuntimeException {
+ public WindowLeaked(String msg) {
+ super(msg);
+ }
+}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index dd6b537..52d79f8 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -16,36 +16,17 @@
package android.view;
-import android.app.ActivityManager;
-import android.content.ComponentCallbacks2;
-import android.content.res.CompatibilityInfo;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.opengl.ManagedEGLContext;
-import android.os.IBinder;
-import android.os.SystemProperties;
-import android.util.AndroidRuntimeException;
-import android.util.Log;
-import android.view.inputmethod.InputMethodManager;
-
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.PrintWriter;
-import java.util.HashMap;
-
-final class WindowLeaked extends AndroidRuntimeException {
- public WindowLeaked(String msg) {
- super(msg);
- }
-}
-
/**
- * Low-level communication with the global system window manager. It implements
- * the ViewManager interface, allowing you to add any View subclass as a
- * top-level window on the screen. Additional window manager specific layout
- * parameters are defined for control over how windows are displayed.
- * It also implemens the WindowManager interface, allowing you to control the
- * displays attached to the device.
+ * Provides low-level communication with the system window manager for
+ * operations that are bound to a particular context, display or parent window.
+ * Instances of this object are sensitive to the compatibility info associated
+ * with the running application.
+ *
+ * This object implements the {@link ViewManager} interface,
+ * allowing you to add any View subclass as a top-level window on the screen.
+ * Additional window manager specific layout parameters are defined for
+ * control over how windows are displayed. It also implements the {@link WindowManager}
+ * interface, allowing you to control the displays attached to the device.
*
* <p>Applications will not normally use WindowManager directly, instead relying
* on the higher-level facilities in {@link android.app.Activity} and
@@ -53,607 +34,58 @@ final class WindowLeaked extends AndroidRuntimeException {
*
* <p>Even for low-level window manager access, it is almost never correct to use
* this class. For example, {@link android.app.Activity#getWindowManager}
- * provides a ViewManager for adding windows that are associated with that
+ * provides a window manager for adding windows that are associated with that
* activity -- the window manager will not normally allow you to add arbitrary
* windows that are not associated with an activity.
- *
+ *
+ * @see WindowManager
+ * @see WindowManagerGlobal
* @hide
*/
-public class WindowManagerImpl implements WindowManager {
- /**
- * The user is navigating with keys (not the touch screen), so
- * navigational focus should be shown.
- */
- public static final int RELAYOUT_RES_IN_TOUCH_MODE = 0x1;
- /**
- * This is the first time the window is being drawn,
- * so the client must call drawingFinished() when done
- */
- public static final int RELAYOUT_RES_FIRST_TIME = 0x2;
- /**
- * The window manager has changed the surface from the last call.
- */
- public static final int RELAYOUT_RES_SURFACE_CHANGED = 0x4;
- /**
- * The window manager is currently animating. It will call
- * IWindow.doneAnimating() when done.
- */
- public static final int RELAYOUT_RES_ANIMATING = 0x8;
-
- /**
- * Flag for relayout: the client will be later giving
- * internal insets; as a result, the window will not impact other window
- * layouts until the insets are given.
- */
- public static final int RELAYOUT_INSETS_PENDING = 0x1;
-
- /**
- * Flag for relayout: the client may be currently using the current surface,
- * so if it is to be destroyed as a part of the relayout the destroy must
- * be deferred until later. The client will call performDeferredDestroy()
- * when it is okay.
- */
- public static final int RELAYOUT_DEFER_SURFACE_DESTROY = 0x2;
-
- public static final int ADD_FLAG_APP_VISIBLE = 0x2;
- public static final int ADD_FLAG_IN_TOUCH_MODE = RELAYOUT_RES_IN_TOUCH_MODE;
-
- public static final int ADD_OKAY = 0;
- public static final int ADD_BAD_APP_TOKEN = -1;
- public static final int ADD_BAD_SUBWINDOW_TOKEN = -2;
- public static final int ADD_NOT_APP_TOKEN = -3;
- public static final int ADD_APP_EXITING = -4;
- public static final int ADD_DUPLICATE_ADD = -5;
- public static final int ADD_STARTING_NOT_NEEDED = -6;
- public static final int ADD_MULTIPLE_SINGLETON = -7;
- public static final int ADD_PERMISSION_DENIED = -8;
-
- private View[] mViews;
- private ViewRootImpl[] mRoots;
- private WindowManager.LayoutParams[] mParams;
- private boolean mNeedsEglTerminate;
-
- private Runnable mSystemPropertyUpdater = null;
-
- private final static Object sLock = new Object();
- private final static WindowManagerImpl sWindowManager = new WindowManagerImpl();
- private final static HashMap<CompatibilityInfo, WindowManager> sCompatWindowManagers
- = new HashMap<CompatibilityInfo, WindowManager>();
-
- static class CompatModeWrapper implements WindowManager {
- private final WindowManagerImpl mWindowManager;
- private final Display mDefaultDisplay;
- private final CompatibilityInfoHolder mCompatibilityInfo;
-
- CompatModeWrapper(WindowManager wm, CompatibilityInfoHolder ci) {
- mWindowManager = wm instanceof CompatModeWrapper
- ? ((CompatModeWrapper)wm).mWindowManager : (WindowManagerImpl)wm;
-
- // Use the original display if there is no compatibility mode
- // to apply, or the underlying window manager is already a
- // compatibility mode wrapper. (We assume that if it is a
- // wrapper, it is applying the same compatibility mode.)
- if (ci == null) {
- mDefaultDisplay = mWindowManager.getDefaultDisplay();
- } else {
- //mDefaultDisplay = mWindowManager.getDefaultDisplay();
- mDefaultDisplay = Display.createCompatibleDisplay(
- mWindowManager.getDefaultDisplay().getDisplayId(), ci);
- }
-
- mCompatibilityInfo = ci;
- }
-
- @Override
- public void addView(View view, android.view.ViewGroup.LayoutParams params) {
- mWindowManager.addView(view, params, mCompatibilityInfo);
- }
-
- @Override
- public void updateViewLayout(View view, android.view.ViewGroup.LayoutParams params) {
- mWindowManager.updateViewLayout(view, params);
-
- }
-
- @Override
- public void removeView(View view) {
- mWindowManager.removeView(view);
- }
-
- @Override
- public Display getDefaultDisplay() {
- return mDefaultDisplay;
- }
-
- @Override
- public void removeViewImmediate(View view) {
- mWindowManager.removeViewImmediate(view);
- }
-
- @Override
- public boolean isHardwareAccelerated() {
- return mWindowManager.isHardwareAccelerated();
- }
+public final class WindowManagerImpl implements WindowManager {
+ private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
+ private final Display mDisplay;
+ private final Window mParentWindow;
+ public WindowManagerImpl(Display display) {
+ this(display, null);
}
- public static WindowManagerImpl getDefault() {
- return sWindowManager;
+ private WindowManagerImpl(Display display, Window parentWindow) {
+ mDisplay = display;
+ mParentWindow = parentWindow;
}
- public static WindowManager getDefault(CompatibilityInfo compatInfo) {
- CompatibilityInfoHolder cih = new CompatibilityInfoHolder();
- cih.set(compatInfo);
- if (cih.getIfNeeded() == null) {
- return sWindowManager;
- }
-
- synchronized (sLock) {
- // NOTE: It would be cleaner to move the implementation of
- // WindowManagerImpl into a static inner class, and have this
- // public impl just call into that. Then we can make multiple
- // instances of WindowManagerImpl for compat mode rather than
- // having to make wrappers.
- WindowManager wm = sCompatWindowManagers.get(compatInfo);
- if (wm == null) {
- wm = new CompatModeWrapper(sWindowManager, cih);
- sCompatWindowManagers.put(compatInfo, wm);
- }
- return wm;
- }
+ public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
+ return new WindowManagerImpl(mDisplay, parentWindow);
}
- public static WindowManager getDefault(CompatibilityInfoHolder compatInfo) {
- return new CompatModeWrapper(sWindowManager, compatInfo);
- }
-
- public boolean isHardwareAccelerated() {
- return false;
- }
-
- public void addView(View view) {
- addView(view, new WindowManager.LayoutParams(
- WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE));
+ public WindowManagerImpl createPresentationWindowManager(Display display) {
+ return new WindowManagerImpl(display, mParentWindow);
}
+ @Override
public void addView(View view, ViewGroup.LayoutParams params) {
- addView(view, params, null, false);
- }
-
- public void addView(View view, ViewGroup.LayoutParams params, CompatibilityInfoHolder cih) {
- addView(view, params, cih, false);
- }
-
- private void addView(View view, ViewGroup.LayoutParams params,
- CompatibilityInfoHolder cih, boolean nest) {
- if (false) Log.v("WindowManager", "addView view=" + view);
-
- if (!(params instanceof WindowManager.LayoutParams)) {
- throw new IllegalArgumentException(
- "Params must be WindowManager.LayoutParams");
- }
-
- final WindowManager.LayoutParams wparams
- = (WindowManager.LayoutParams)params;
-
- ViewRootImpl root;
- View panelParentView = null;
-
- synchronized (this) {
- // Start watching for system property changes.
- if (mSystemPropertyUpdater == null) {
- mSystemPropertyUpdater = new Runnable() {
- @Override public void run() {
- synchronized (this) {
- synchronized (this) {
- for (ViewRootImpl root : mRoots) {
- root.loadSystemProperties();
- }
- }
- }
- }
- };
- SystemProperties.addChangeCallback(mSystemPropertyUpdater);
- }
-
- // Here's an odd/questionable case: if someone tries to add a
- // view multiple times, then we simply bump up a nesting count
- // and they need to remove the view the corresponding number of
- // times to have it actually removed from the window manager.
- // This is useful specifically for the notification manager,
- // which can continually add/remove the same view as a
- // notification gets updated.
- int index = findViewLocked(view, false);
- if (index >= 0) {
- if (!nest) {
- throw new IllegalStateException("View " + view
- + " has already been added to the window manager.");
- }
- root = mRoots[index];
- root.mAddNesting++;
- // Update layout parameters.
- view.setLayoutParams(wparams);
- root.setLayoutParams(wparams, true);
- return;
- }
-
- // 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<count; i++) {
- if (mRoots[i].mWindow.asBinder() == wparams.token) {
- panelParentView = mViews[i];
- }
- }
- }
-
- root = new ViewRootImpl(view.getContext());
- root.mAddNesting = 1;
- if (cih == null) {
- root.mCompatibilityInfo = new CompatibilityInfoHolder();
- } else {
- root.mCompatibilityInfo = cih;
- }
-
- view.setLayoutParams(wparams);
-
- if (mViews == null) {
- index = 1;
- mViews = new View[1];
- mRoots = new ViewRootImpl[1];
- mParams = new WindowManager.LayoutParams[1];
- } else {
- index = mViews.length + 1;
- Object[] old = mViews;
- mViews = new View[index];
- System.arraycopy(old, 0, mViews, 0, index-1);
- old = mRoots;
- mRoots = new ViewRootImpl[index];
- System.arraycopy(old, 0, mRoots, 0, index-1);
- old = mParams;
- mParams = new WindowManager.LayoutParams[index];
- System.arraycopy(old, 0, mParams, 0, index-1);
- }
- index--;
-
- mViews[index] = view;
- mRoots[index] = root;
- mParams[index] = wparams;
- }
- // do this last because it fires off messages to start doing things
- root.setView(view, wparams, panelParentView);
+ mGlobal.addView(view, params, mDisplay, mParentWindow);
}
+ @Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
- if (!(params instanceof WindowManager.LayoutParams)) {
- throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
- }
-
- final WindowManager.LayoutParams wparams
- = (WindowManager.LayoutParams)params;
-
- view.setLayoutParams(wparams);
-
- synchronized (this) {
- int index = findViewLocked(view, true);
- ViewRootImpl root = mRoots[index];
- mParams[index] = wparams;
- root.setLayoutParams(wparams, false);
- }
+ mGlobal.updateViewLayout(view, params);
}
+ @Override
public void removeView(View view) {
- synchronized (this) {
- int index = findViewLocked(view, true);
- View curView = removeViewLocked(index);
- if (curView == view) {
- return;
- }
-
- throw new IllegalStateException("Calling with view " + view
- + " but the ViewAncestor is attached to " + curView);
- }
+ mGlobal.removeView(view, false);
}
+ @Override
public void removeViewImmediate(View view) {
- synchronized (this) {
- int index = findViewLocked(view, true);
- ViewRootImpl root = mRoots[index];
- View curView = root.getView();
-
- root.mAddNesting = 0;
-
- if (view != null) {
- InputMethodManager imm = InputMethodManager.getInstance(view.getContext());
- if (imm != null) {
- imm.windowDismissed(mViews[index].getWindowToken());
- }
- }
-
- root.die(true);
- finishRemoveViewLocked(curView, index);
- if (curView == view) {
- return;
- }
-
- throw new IllegalStateException("Calling with view " + view
- + " but the ViewAncestor is attached to " + curView);
- }
+ mGlobal.removeView(view, true);
}
-
- View removeViewLocked(int index) {
- ViewRootImpl root = mRoots[index];
- View view = root.getView();
- // Don't really remove until we have matched all calls to add().
- root.mAddNesting--;
- if (root.mAddNesting > 0) {
- return view;
- }
-
- if (view != null) {
- InputMethodManager imm = InputMethodManager.getInstance(view.getContext());
- if (imm != null) {
- imm.windowDismissed(mViews[index].getWindowToken());
- }
- }
- root.die(false);
- finishRemoveViewLocked(view, index);
- return view;
- }
-
- void finishRemoveViewLocked(View view, int index) {
- final int count = mViews.length;
-
- // remove it from the list
- View[] tmpViews = new View[count-1];
- removeItem(tmpViews, mViews, index);
- mViews = tmpViews;
-
- ViewRootImpl[] tmpRoots = new ViewRootImpl[count-1];
- removeItem(tmpRoots, mRoots, index);
- mRoots = tmpRoots;
-
- WindowManager.LayoutParams[] tmpParams
- = new WindowManager.LayoutParams[count-1];
- removeItem(tmpParams, mParams, index);
- mParams = tmpParams;
-
- if (view != null) {
- view.assignParent(null);
- // func doesn't allow null... does it matter if we clear them?
- //view.setLayoutParams(null);
- }
- }
-
- public void closeAll(IBinder token, String who, String what) {
- synchronized (this) {
- if (mViews == null)
- return;
-
- int count = mViews.length;
- //Log.i("foo", "Closing all windows of " + token);
- for (int i=0; i<count; i++) {
- //Log.i("foo", "@ " + i + " token " + mParams[i].token
- // + " view " + mRoots[i].getView());
- if (token == null || mParams[i].token == token) {
- ViewRootImpl root = mRoots[i];
- root.mAddNesting = 1;
-
- //Log.i("foo", "Force closing " + root);
- if (who != null) {
- WindowLeaked leak = new WindowLeaked(
- what + " " + who + " has leaked window "
- + root.getView() + " that was originally added here");
- leak.setStackTrace(root.getLocation().getStackTrace());
- Log.e("WindowManager", leak.getMessage(), leak);
- }
-
- removeViewLocked(i);
- i--;
- count--;
- }
- }
- }
- }
-
- /**
- * @param level See {@link android.content.ComponentCallbacks}
- *
- * @hide
- */
- public void startTrimMemory(int level) {
- if (HardwareRenderer.isAvailable()) {
- // On low-end gfx devices we trim when memory is moderate;
- // on high-end devices we do this when low.
- if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE
- || (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE
- && !ActivityManager.isHighEndGfx(getDefaultDisplay()))) {
- // Destroy all hardware surfaces and resources associated to
- // known windows
- synchronized (this) {
- if (mViews == null) return;
- int count = mViews.length;
- for (int i = 0; i < count; i++) {
- mRoots[i].terminateHardwareResources();
- }
- }
- // Force a full memory flush
- mNeedsEglTerminate = true;
- HardwareRenderer.startTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
- return;
- }
-
- HardwareRenderer.startTrimMemory(level);
- }
- }
-
- /**
- * @hide
- */
- public void endTrimMemory() {
- HardwareRenderer.endTrimMemory();
-
- if (mNeedsEglTerminate) {
- ManagedEGLContext.doTerminate();
- mNeedsEglTerminate = false;
- }
- }
-
- /**
- * @hide
- */
- public void trimLocalMemory() {
- synchronized (this) {
- if (mViews == null) return;
- int count = mViews.length;
- for (int i = 0; i < count; i++) {
- mRoots[i].destroyHardwareLayers();
- }
- }
- }
-
- /**
- * @hide
- */
- public void dumpGfxInfo(FileDescriptor fd) {
- FileOutputStream fout = new FileOutputStream(fd);
- PrintWriter pw = new PrintWriter(fout);
- try {
- synchronized (this) {
- if (mViews != null) {
- final int count = mViews.length;
-
- 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);
-
- 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];
-
- for (int i = 0; i < count; i++) {
- ViewRootImpl root = mRoots[i];
- root.dumpGfxInfo(info);
-
- 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");
-
- 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();
- }
- }
-
- private static String getWindowName(ViewRootImpl root) {
- return root.mWindowAttributes.getTitle() + "/" +
- root.getClass().getName() + '@' + Integer.toHexString(root.hashCode());
- }
-
- public void setStoppedState(IBinder token, boolean stopped) {
- synchronized (this) {
- if (mViews == null)
- return;
- 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);
- }
- }
- }
- }
-
- public void reportNewConfiguration(Configuration config) {
- synchronized (this) {
- int count = mViews.length;
- config = new Configuration(config);
- for (int i=0; i<count; i++) {
- ViewRootImpl root = mRoots[i];
- root.requestUpdateConfiguration(config);
- }
- }
- }
-
- public WindowManager.LayoutParams getRootViewLayoutParameter(View view) {
- ViewParent vp = view.getParent();
- while (vp != null && !(vp instanceof ViewRootImpl)) {
- vp = vp.getParent();
- }
-
- if (vp == null) return null;
-
- ViewRootImpl vr = (ViewRootImpl)vp;
-
- int N = mRoots.length;
- for (int i = 0; i < N; ++i) {
- if (mRoots[i] == vr) {
- return mParams[i];
- }
- }
-
- return null;
- }
-
- public void closeAll() {
- closeAll(null, null, null);
- }
-
+ @Override
public Display getDefaultDisplay() {
- return new Display(Display.DEFAULT_DISPLAY, null);
- }
-
- private static void removeItem(Object[] dst, Object[] src, int index) {
- if (dst.length > 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);
- }
- }
- }
-
- private int findViewLocked(View view, boolean required) {
- synchronized (this) {
- final int count = mViews != null ? mViews.length : 0;
- for (int i=0; i<count; i++) {
- if (mViews[i] == view) {
- return i;
- }
- }
- if (required) {
- throw new IllegalArgumentException(
- "View not attached to window manager");
- }
- return -1;
- }
+ return mDisplay;
}
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 09948b8..82f07c7 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -22,7 +22,6 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.IBinder;
-import android.os.LocalPowerManager;
import android.os.Looper;
import android.view.animation.Animation;
@@ -115,16 +114,16 @@ public interface WindowManagerPolicy {
public final static int ACTION_PASS_TO_USER = 0x00000001;
/**
- * This key event should extend the user activity timeout and turn the lights on.
+ * This key event should wake the device.
* To be returned from {@link #interceptKeyBeforeQueueing}.
* Do not return this and {@link #ACTION_GO_TO_SLEEP} or {@link #ACTION_PASS_TO_USER}.
*/
- public final static int ACTION_POKE_USER_ACTIVITY = 0x00000002;
+ public final static int ACTION_WAKE_UP = 0x00000002;
/**
* This key event should put the device to sleep (and engage keyguard if necessary)
* To be returned from {@link #interceptKeyBeforeQueueing}.
- * Do not return this and {@link #ACTION_POKE_USER_ACTIVITY} or {@link #ACTION_PASS_TO_USER}.
+ * Do not return this and {@link #ACTION_WAKE_UP} or {@link #ACTION_PASS_TO_USER}.
*/
public final static int ACTION_GO_TO_SLEEP = 0x00000004;
@@ -339,6 +338,12 @@ public interface WindowManagerPolicy {
* Check whether the process hosting this window is currently alive.
*/
public boolean isAlive();
+
+ /**
+ * Check if window is on {@link Display#DEFAULT_DISPLAY}.
+ * @return true if window is on default display.
+ */
+ public boolean isDefaultDisplay();
}
/**
@@ -391,8 +396,8 @@ public interface WindowManagerPolicy {
*/
public void switchKeyboardLayout(int deviceId, int direction);
- public void shutdown();
- public void rebootSafeMode();
+ public void shutdown(boolean confirm);
+ public void rebootSafeMode(boolean confirm);
}
/**
@@ -473,26 +478,24 @@ public interface WindowManagerPolicy {
* Perform initialization of the policy.
*
* @param context The system context we are running in.
- * @param powerManager
*/
public void init(Context context, IWindowManager windowManager,
- WindowManagerFuncs windowManagerFuncs,
- LocalPowerManager powerManager);
+ WindowManagerFuncs windowManagerFuncs);
/**
* Called by window manager once it has the initial, default native
* display dimensions.
*/
- public void setInitialDisplaySize(Display display, int width, int height);
+ public void setInitialDisplaySize(Display display, int width, int height, int density);
/**
* Check permissions when adding a window.
*
* @param attrs The window's LayoutParams.
*
- * @return {@link WindowManagerImpl#ADD_OKAY} if the add can proceed;
+ * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
* else an error code, usually
- * {@link WindowManagerImpl#ADD_PERMISSION_DENIED}, to abort the add.
+ * {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add.
*/
public int checkAddPermission(WindowManager.LayoutParams attrs);
@@ -553,7 +556,14 @@ public interface WindowManagerPolicy {
* allowed to be in.
*/
public int getMaxWallpaperLayer();
-
+
+ /**
+ * Return the window layer at which windows appear above the normal
+ * universe (that is no longer impacted by the universe background
+ * transform).
+ */
+ 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.
@@ -658,7 +668,7 @@ public interface WindowManagerPolicy {
* @param win The window being added.
* @param attrs The window's LayoutParams.
*
- * @return {@link WindowManagerImpl#ADD_OKAY} if the add can proceed, else an
+ * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed, else an
* error code to abort the add.
*/
public int prepareAddWindowLw(WindowState win,
@@ -703,7 +713,7 @@ public interface WindowManagerPolicy {
* @param isScreenOn True if the screen is already on
*
* @return The bitwise or of the {@link #ACTION_PASS_TO_USER},
- * {@link #ACTION_POKE_USER_ACTIVITY} and {@link #ACTION_GO_TO_SLEEP} flags.
+ * {@link #ACTION_WAKE_UP} and {@link #ACTION_GO_TO_SLEEP} flags.
*/
public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn);
@@ -717,7 +727,7 @@ public interface WindowManagerPolicy {
* @param policyFlags The policy flags associated with the motion.
*
* @return The bitwise or of the {@link #ACTION_PASS_TO_USER},
- * {@link #ACTION_POKE_USER_ACTIVITY} and {@link #ACTION_GO_TO_SLEEP} flags.
+ * {@link #ACTION_WAKE_UP} and {@link #ACTION_GO_TO_SLEEP} flags.
*/
public int interceptMotionBeforeQueueingWhenScreenOff(int policyFlags);
@@ -758,12 +768,14 @@ public interface WindowManagerPolicy {
/**
* Called when layout of the windows is about to start.
*
+ * @param isDefaultDisplay true if window is on {@link Display#DEFAULT_DISPLAY}.
* @param displayWidth The current full width of the screen.
* @param displayHeight The current full height of the screen.
* @param displayRotation The current rotation being applied to the base
* window.
*/
- public void beginLayoutLw(int displayWidth, int displayHeight, int displayRotation);
+ public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
+ int displayRotation);
/**
* Return the rectangle of the screen currently covered by system decorations.
@@ -823,32 +835,33 @@ public interface WindowManagerPolicy {
static final int FINISH_LAYOUT_REDO_ANIM = 0x0008;
/**
- * Called when animation of the windows is about to start.
+ * Called following layout of all windows before each window has policy applied.
*
* @param displayWidth The current full width of the screen.
* @param displayHeight The current full height of the screen.
*/
- public void beginAnimationLw(int displayWidth, int displayHeight);
+ public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight);
/**
- * Called each time a window is animating.
+ * Called following layout of all window to apply policy to each window.
*
* @param win The window being positioned.
* @param attrs The LayoutParams of the window.
*/
- public void animatingWindowLw(WindowState win,
+ public void applyPostLayoutPolicyLw(WindowState win,
WindowManager.LayoutParams attrs);
/**
- * Called when animation of the windows is finished. If in this function you do
- * something that may have modified the animation state of another window,
- * be sure to return true in order to perform another animation frame.
+ * Called following layout of all windows and after policy has been applied
+ * to each window. If in this function you do
+ * something that may have modified the animation state of another window,
+ * be sure to return non-zero in order to perform another pass through layout.
*
* @return Return any bit set of {@link #FINISH_LAYOUT_REDO_LAYOUT},
* {@link #FINISH_LAYOUT_REDO_CONFIG}, {@link #FINISH_LAYOUT_REDO_WALLPAPER},
* or {@link #FINISH_LAYOUT_REDO_ANIM}.
*/
- public int finishAnimationLw();
+ public int finishPostLayoutPolicyLw();
/**
* Return true if it is okay to perform animations for an app transition
@@ -1061,9 +1074,9 @@ public interface WindowManagerPolicy {
* Inform the policy that the user has chosen a preferred orientation ("rotation lock").
*
* @param mode One of {@link WindowManagerPolicy#USER_ROTATION_LOCKED} or
- * {@link * WindowManagerPolicy#USER_ROTATION_FREE}.
+ * {@link WindowManagerPolicy#USER_ROTATION_FREE}.
* @param rotation One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
- * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
+ * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
*/
public void setUserRotationMode(int mode, int rotation);
@@ -1086,36 +1099,27 @@ public interface WindowManagerPolicy {
public void lockNow();
/**
- * Check to see if a screensaver should be run instead of powering off the screen on timeout.
- *
- * @return true if the screensaver should run, false if the screen should turn off.
- *
- * @hide
- */
- public boolean isScreenSaverEnabled();
-
- /**
- * Start the screensaver (if it is enabled and not yet running).
- *
- * @return Whether the screensaver was successfully started.
- *
+ * Set the last used input method window state. This state is used to make IME transition
+ * smooth.
* @hide
*/
- public boolean startScreenSaver();
+ public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
/**
- * Stop the screensaver if it is running.
- *
- * @hide
+ * Returns whether magnification can be applied to the given window type.
+ *
+ * @param attrs The window's LayoutParams.
+ * @return Whether magnification can be applied.
*/
- public void stopScreenSaver();
+ public boolean canMagnifyWindowLw(WindowManager.LayoutParams attrs);
/**
- * Set the last used input method window state. This state is used to make IME transition
- * smooth.
- * @hide
+ * Called when the current user changes. Guaranteed to be called before the broadcast
+ * of the new user id is made to all listeners.
+ *
+ * @param newUserId The id of the incoming user.
*/
- public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
+ public void setCurrentUserLw(int newUserId);
/**
* Print the WindowManagerPolicy's state into the given stream.
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 1a2a194..1500905 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -424,6 +424,28 @@ import java.util.List;
* </ul>
* </p>
* <p>
+ * <b>Touch interaction start</b> - represents the event of starting a touch
+ * interaction, which is the user starts touching the screen.</br>
+ * <em>Type:</em> {@link #TYPE_TOUCH_INTERACTION_START}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch interaction end</b> - represents the event of ending a touch
+ * interaction, which is the user stops touching the screen.</br>
+ * <em>Type:</em> {@link #TYPE_TOUCH_INTERACTION_END}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
* <b>Touch exploration gesture start</b> - represents the event of starting a touch
* exploring gesture.</br>
* <em>Type:</em> {@link #TYPE_TOUCH_EXPLORATION_GESTURE_START}</br>
@@ -431,15 +453,8 @@ import java.util.List;
* <ul>
* <li>{@link #getEventType()} - The type of the event.</li>
* </ul>
- * <em>Note:</em> This event type is not dispatched to descendants though
- * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
- * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event
- * source {@link android.view.View} and the sub-tree rooted at it will not receive
- * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent)
- * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add
- * text content to such events is by setting the
- * {@link android.R.styleable#View_contentDescription contentDescription} of the source
- * view.</br>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
* </p>
* <p>
* <b>Touch exploration gesture end</b> - represents the event of ending a touch
@@ -449,15 +464,30 @@ import java.util.List;
* <ul>
* <li>{@link #getEventType()} - The type of the event.</li>
* </ul>
- * <em>Note:</em> This event type is not dispatched to descendants though
- * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent)
- * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event
- * source {@link android.view.View} and the sub-tree rooted at it will not receive
- * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent)
- * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add
- * text content to such events is by setting the
- * {@link android.R.styleable#View_contentDescription contentDescription} of the source
- * view.</br>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch gesture detection start</b> - represents the event of starting a user
+ * gesture detection.</br>
+ * <em>Type:</em> {@link #TYPE_GESTURE_DETECTION_START}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
+ * </p>
+ * <p>
+ * <b>Touch gesture detection end</b> - represents the event of ending a user
+ * gesture detection.</br>
+ * <em>Type:</em> {@link #TYPE_GESTURE_DETECTION_END}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * </ul>
+ * <em>Note:</em> This event is fired only by the system and is not passed to the
+ * view tree to be populated.</br>
* </p>
* <p>
* <b>MISCELLANEOUS TYPES</b></br>
@@ -610,6 +640,26 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 0x00020000;
/**
+ * Represents the event of beginning gesture detection.
+ */
+ public static final int TYPE_GESTURE_DETECTION_START = 0x00040000;
+
+ /**
+ * Represents the event of ending gesture detection.
+ */
+ public static final int TYPE_GESTURE_DETECTION_END = 0x00080000;
+
+ /**
+ * Represents the event of the user starting to touch the screen.
+ */
+ public static final int TYPE_TOUCH_INTERACTION_START = 0x00100000;
+
+ /**
+ * Represents the event of the user ending to touch the screen.
+ */
+ public static final int TYPE_TOUCH_INTERACTION_END = 0x00200000;
+
+ /**
* Mask for {@link AccessibilityEvent} all types.
*
* @see #TYPE_VIEW_CLICKED
@@ -628,6 +678,10 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* @see #TYPE_VIEW_TEXT_SELECTION_CHANGED
* @see #TYPE_ANNOUNCEMENT
* @see #TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
+ * @see #TYPE_GESTURE_DETECTION_START
+ * @see #TYPE_GESTURE_DETECTION_END
+ * @see #TYPE_TOUCH_INTERACTION_START
+ * @see #TYPE_TOUCH_INTERACTION_END
*/
public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
@@ -1120,6 +1174,14 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
return "TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED";
case TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY:
return "TYPE_CURRENT_AT_GRANULARITY_MOVEMENT_CHANGED";
+ case TYPE_GESTURE_DETECTION_START:
+ return "TYPE_GESTURE_DETECTION_START";
+ case TYPE_GESTURE_DETECTION_END:
+ return "TYPE_GESTURE_DETECTION_END";
+ case TYPE_TOUCH_INTERACTION_START:
+ return "TYPE_TOUCH_INTERACTION_START";
+ case TYPE_TOUCH_INTERACTION_END:
+ return "TYPE_TOUCH_INTERACTION_END";
default:
return null;
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 77fd12a..732699b 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -27,6 +27,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Log;
import android.view.IWindow;
import android.view.View;
@@ -79,6 +80,8 @@ public final class AccessibilityManager {
final IAccessibilityManager mService;
+ final int mUserId;
+
final Handler mHandler;
boolean mIsEnabled;
@@ -129,35 +132,72 @@ 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.
+ * <p>
+ * Note: Calling this method requires INTERACT_ACROSS_USERS_FULL or
+ * INTERACT_ACROSS_USERS permission.
+ * </p>
+ * @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).
*
+ * @param context Context in which this manager operates.
+ *
* @hide
*/
public static AccessibilityManager getInstance(Context context) {
synchronized (sInstanceSync) {
if (sInstance == null) {
- IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
- IAccessibilityManager service = IAccessibilityManager.Stub.asInterface(iBinder);
- sInstance = new AccessibilityManager(context, service);
+ createSingletonInstance(context, UserHandle.myUserId());
}
}
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.
*
* @param context A {@link Context}.
* @param service An interface to the backing service.
+ * @param userId User id under which to run.
*
* @hide
*/
- public AccessibilityManager(Context context, IAccessibilityManager service) {
+ public AccessibilityManager(Context context, IAccessibilityManager service, int userId) {
mHandler = new MyHandler(context.getMainLooper());
mService = service;
+ mUserId = userId;
try {
- final int stateFlags = mService.addClient(mClient);
+ final int stateFlags = mService.addClient(mClient, userId);
setState(stateFlags);
} catch (RemoteException re) {
Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
@@ -222,7 +262,7 @@ public final class AccessibilityManager {
// client using it is called through Binder from another process. Example: MMS
// app adds a SMS notification and the NotificationManagerService calls this method
long identityToken = Binder.clearCallingIdentity();
- doRecycle = mService.sendAccessibilityEvent(event);
+ doRecycle = mService.sendAccessibilityEvent(event, mUserId);
Binder.restoreCallingIdentity(identityToken);
if (DEBUG) {
Log.i(LOG_TAG, event + " sent");
@@ -244,7 +284,7 @@ public final class AccessibilityManager {
throw new IllegalStateException("Accessibility off. Did you forget to check that?");
}
try {
- mService.interrupt();
+ mService.interrupt(mUserId);
if (DEBUG) {
Log.i(LOG_TAG, "Requested interrupt from all services");
}
@@ -280,7 +320,7 @@ public final class AccessibilityManager {
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() {
List<AccessibilityServiceInfo> services = null;
try {
- services = mService.getInstalledAccessibilityServiceList();
+ services = mService.getInstalledAccessibilityServiceList(mUserId);
if (DEBUG) {
Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
}
@@ -307,7 +347,7 @@ public final class AccessibilityManager {
int feedbackTypeFlags) {
List<AccessibilityServiceInfo> services = null;
try {
- services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags);
+ services = mService.getEnabledAccessibilityServiceList(feedbackTypeFlags, mUserId);
if (DEBUG) {
Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
}
@@ -385,7 +425,7 @@ public final class AccessibilityManager {
public int addAccessibilityInteractionConnection(IWindow windowToken,
IAccessibilityInteractionConnection connection) {
try {
- return mService.addAccessibilityInteractionConnection(windowToken, connection);
+ return mService.addAccessibilityInteractionConnection(windowToken, connection, mUserId);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while adding an accessibility interaction connection. ", re);
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 3ad3a55..1dc2487 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -365,6 +365,8 @@ public class AccessibilityNodeInfo implements Parcelable {
private int mWindowId = UNDEFINED;
private long mSourceNodeId = ROOT_NODE_ID;
private long mParentNodeId = ROOT_NODE_ID;
+ private long mLabelForId = ROOT_NODE_ID;
+ private long mLabeledById = ROOT_NODE_ID;
private int mBooleanProperties;
private final Rect mBoundsInParent = new Rect();
@@ -382,10 +384,6 @@ public class AccessibilityNodeInfo implements Parcelable {
private int mConnectionId = UNDEFINED;
- // TODO: These are a workaround for 6623031. Remove when fixed.
- private int mActualAndReportedWindowLeftDelta;
- private int mActualAndReportedWindowTopDelta;
-
/**
* Hide constructor from clients.
*/
@@ -432,10 +430,6 @@ public class AccessibilityNodeInfo implements Parcelable {
final int rootAccessibilityViewId =
(root != null) ? root.getAccessibilityViewId() : UNDEFINED;
mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
- if (root != null) {
- mActualAndReportedWindowLeftDelta = root.getActualAndReportedWindowLeftDelta();
- mActualAndReportedWindowTopDelta = root.getActualAndReportedWindowTopDelta();
- }
}
/**
@@ -833,7 +827,6 @@ public class AccessibilityNodeInfo implements Parcelable {
public void setBoundsInScreen(Rect bounds) {
enforceNotSealed();
mBoundsInScreen.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
- mBoundsInScreen.offset(mActualAndReportedWindowLeftDelta, mActualAndReportedWindowTopDelta);
}
/**
@@ -1242,6 +1235,120 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Sets the view for which the view represented by this info serves as a
+ * label for accessibility purposes.
+ *
+ * @param labeled The view for which this info serves as a label.
+ */
+ public void setLabelFor(View labeled) {
+ setLabelFor(labeled, UNDEFINED);
+ }
+
+ /**
+ * Sets the view for which the view represented by this info serves as a
+ * label for accessibility purposes. If <code>virtualDescendantId</code>
+ * is {@link View#NO_ID} the root is set as the labeled.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report themselves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root whose virtual descendant serves as a label.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setLabelFor(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId = (root != null)
+ ? root.getAccessibilityViewId() : UNDEFINED;
+ mLabelForId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Gets the node info for which the view represented by this info serves as
+ * a label for accessibility purposes.
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ *
+ * @return The labeled info.
+ */
+ public AccessibilityNodeInfo getLabelFor() {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mLabelForId)) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+ mWindowId, mLabelForId, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
+ }
+
+ /**
+ * Sets the view which serves as the label of the view represented by
+ * this info for accessibility purposes.
+ *
+ * @param label The view that labels this node's source.
+ */
+ public void setLabeledBy(View label) {
+ setLabeledBy(label, UNDEFINED);
+ }
+
+ /**
+ * Sets the view which serves as the label of the view represented by
+ * this info for accessibility purposes. If <code>virtualDescendantId</code>
+ * is {@link View#NO_ID} the root is set as the label.
+ * <p>
+ * A virtual descendant is an imaginary View that is reported as a part of the view
+ * hierarchy for accessibility purposes. This enables custom views that draw complex
+ * content to report themselves as a tree of virtual views, thus conveying their
+ * logical structure.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param root The root whose virtual descendant labels this node's source.
+ * @param virtualDescendantId The id of the virtual descendant.
+ */
+ public void setLabeledBy(View root, int virtualDescendantId) {
+ enforceNotSealed();
+ final int rootAccessibilityViewId = (root != null)
+ ? root.getAccessibilityViewId() : UNDEFINED;
+ mLabeledById = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
+ }
+
+ /**
+ * Gets the node info which serves as the label of the view represented by
+ * this info for accessibility purposes.
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ *
+ * @return The label.
+ */
+ public AccessibilityNodeInfo getLabeledBy() {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mLabeledById)) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
+ mWindowId, mLabeledById, FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS);
+ }
+
+ /**
* Gets the value of a boolean property.
*
* @param property The property.
@@ -1343,12 +1450,6 @@ public class AccessibilityNodeInfo implements Parcelable {
case View.FOCUS_RIGHT:
case View.FOCUS_FORWARD:
case View.FOCUS_BACKWARD:
- case View.ACCESSIBILITY_FOCUS_DOWN:
- case View.ACCESSIBILITY_FOCUS_UP:
- case View.ACCESSIBILITY_FOCUS_LEFT:
- case View.ACCESSIBILITY_FOCUS_RIGHT:
- case View.ACCESSIBILITY_FOCUS_FORWARD:
- case View.ACCESSIBILITY_FOCUS_BACKWARD:
return;
default:
throw new IllegalArgumentException("Unknown direction: " + direction);
@@ -1477,6 +1578,8 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeLong(mSourceNodeId);
parcel.writeInt(mWindowId);
parcel.writeLong(mParentNodeId);
+ parcel.writeLong(mLabelForId);
+ parcel.writeLong(mLabeledById);
parcel.writeInt(mConnectionId);
SparseLongArray childIds = mChildNodeIds;
@@ -1522,6 +1625,8 @@ public class AccessibilityNodeInfo implements Parcelable {
mSealed = other.mSealed;
mSourceNodeId = other.mSourceNodeId;
mParentNodeId = other.mParentNodeId;
+ mLabelForId = other.mLabelForId;
+ mLabeledById = other.mLabeledById;
mWindowId = other.mWindowId;
mConnectionId = other.mConnectionId;
mBoundsInParent.set(other.mBoundsInParent);
@@ -1549,6 +1654,8 @@ public class AccessibilityNodeInfo implements Parcelable {
mSourceNodeId = parcel.readLong();
mWindowId = parcel.readInt();
mParentNodeId = parcel.readLong();
+ mLabelForId = parcel.readLong();
+ mLabeledById = parcel.readLong();
mConnectionId = parcel.readInt();
SparseLongArray childIds = mChildNodeIds;
@@ -1587,6 +1694,8 @@ public class AccessibilityNodeInfo implements Parcelable {
mSealed = false;
mSourceNodeId = ROOT_NODE_ID;
mParentNodeId = ROOT_NODE_ID;
+ mLabelForId = ROOT_NODE_ID;
+ mLabeledById = ROOT_NODE_ID;
mWindowId = UNDEFINED;
mConnectionId = UNDEFINED;
mMovementGranularities = 0;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
index b3f3cee..688cbdf 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
@@ -132,60 +132,4 @@ public abstract class AccessibilityNodeProvider {
int virtualViewId) {
return null;
}
-
- /**
- * Finds the accessibility focused {@link AccessibilityNodeInfo}. The search is
- * relative to the virtual view, i.e. a descendant of the host View, with the
- * given <code>virtualViewId</code> or the host View itself
- * <code>virtualViewId</code> equals to {@link View#NO_ID}.
- *
- * <strong>Note:</strong> Normally the system is responsible to transparently find
- * accessibility focused view starting from a given root but for virtual view
- * hierarchies it is a responsibility of this provider's implementor to find
- * the accessibility focused virtual view.
- *
- * @param virtualViewId A client defined virtual view id which defined
- * the root of the tree in which to perform the search.
- * @return A list of node info.
- *
- * @see #createAccessibilityNodeInfo(int)
- * @see AccessibilityNodeInfo
- *
- * @hide
- */
- public AccessibilityNodeInfo findAccessibilityFocus(int virtualViewId) {
- return null;
- }
-
- /**
- * Finds {@link AccessibilityNodeInfo} to take accessibility focus in the given
- * <code>direction</code>. The search is relative to the virtual view, i.e. a
- * descendant of the host View, with the given <code>virtualViewId</code> or
- * the host View itself <code>virtualViewId</code> equals to {@link View#NO_ID}.
- *
- * <strong>Note:</strong> Normally the system is responsible to transparently find
- * the next view to take accessibility focus but for virtual view hierarchies
- * it is a responsibility of this provider's implementor to compute the next
- * focusable.
- *
- * @param direction The direction in which to search for a focus candidate.
- * Values are
- * {@link View#ACCESSIBILITY_FOCUS_FORWARD},
- * {@link View#ACCESSIBILITY_FOCUS_BACKWARD},
- * {@link View#ACCESSIBILITY_FOCUS_UP},
- * {@link View#ACCESSIBILITY_FOCUS_DOWN},
- * {@link View#ACCESSIBILITY_FOCUS_LEFT},
- * {@link View#ACCESSIBILITY_FOCUS_RIGHT}.
- * @param virtualViewId A client defined virtual view id which defined
- * the root of the tree in which to perform the search.
- * @return A list of node info.
- *
- * @see #createAccessibilityNodeInfo(int)
- * @see AccessibilityNodeInfo
- *
- * @hide
- */
- public AccessibilityNodeInfo accessibilityFocusSearch(int direction, int virtualViewId) {
- return null;
- }
}
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index 292702a..9b39300 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -28,25 +28,25 @@ import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
*/
oneway interface IAccessibilityInteractionConnection {
- void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, int windowLeft,
- int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int flags, int interrogatingPid, long interrogatingTid);
+ void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
- void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, int windowLeft,
- int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int flags, int interrogatingPid, long interrogatingTid);
+ void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
- void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int windowLeft,
- int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int flags, int interrogatingPid, long interrogatingTid);
+ void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
- void findFocus(long accessibilityNodeId, int focusType, int windowLeft, int windowTop,
- int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid);
+ void findFocus(long accessibilityNodeId, int focusType, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
- void focusSearch(long accessibilityNodeId, int direction, int windowLeft, int windowTop,
- int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid);
+ void focusSearch(long accessibilityNodeId, int direction, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 5b5134a..c3ef54c 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -20,6 +20,7 @@ package android.view.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.accessibilityservice.IAccessibilityServiceClient;
+import android.content.ComponentName;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -34,18 +35,18 @@ import android.view.IWindow;
*/
interface IAccessibilityManager {
- int addClient(IAccessibilityManagerClient client);
+ int addClient(IAccessibilityManagerClient client, int userId);
- boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent);
+ boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId);
- List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
+ List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId);
- List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType);
+ List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId);
- void interrupt();
+ void interrupt(int userId);
int addAccessibilityInteractionConnection(IWindow windowToken,
- in IAccessibilityInteractionConnection connection);
+ in IAccessibilityInteractionConnection connection, int userId);
void removeAccessibilityInteractionConnection(IWindow windowToken);
@@ -53,4 +54,7 @@ interface IAccessibilityManager {
in AccessibilityServiceInfo info);
void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
+
+ void temporaryEnableAccessibilityStateUntilKeyguardRemoved(in ComponentName service,
+ boolean touchExplorationEnabled);
}
diff --git a/core/java/android/view/animation/RotateAnimation.java b/core/java/android/view/animation/RotateAnimation.java
index 67e0374..3c325d9 100644
--- a/core/java/android/view/animation/RotateAnimation.java
+++ b/core/java/android/view/animation/RotateAnimation.java
@@ -22,7 +22,7 @@ import android.util.AttributeSet;
/**
* An animation that controls the rotation of an object. This rotation takes
- * place int the X-Y plane. You can specify the point to use for the center of
+ * place in the X-Y plane. You can specify the point to use for the center of
* the rotation, where (0,0) is the top left point. If not specified, (0,0) is
* the default rotation point.
*
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 131f0ae..08e30aa 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -34,6 +34,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Printer;
+import android.util.Slog;
import android.util.Xml;
import java.io.IOException;
@@ -169,7 +170,10 @@ public final class InputMethodInfo implements Parcelable {
a.getBoolean(com.android.internal.R.styleable
.InputMethod_Subtype_isAuxiliary, false),
a.getBoolean(com.android.internal.R.styleable
- .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false));
+ .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false),
+ a.getInt(com.android.internal.R.styleable
+ .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */)
+ );
if (!subtype.isAuxiliary()) {
mIsAuxIme = false;
}
@@ -194,6 +198,9 @@ public final class InputMethodInfo implements Parcelable {
final InputMethodSubtype subtype = additionalSubtypes.get(i);
if (!mSubtypes.contains(subtype)) {
mSubtypes.add(subtype);
+ } else {
+ Slog.w(TAG, "Duplicated subtype definition found: "
+ + subtype.getLocale() + ", " + subtype.getMode());
}
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index d2cc2d8..3ea6df3 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -16,7 +16,7 @@
package android.view.inputmethod;
-import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInputConnectionWrapper;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodCallback;
@@ -35,6 +35,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.PrintWriterPrinter;
@@ -225,6 +226,13 @@ public final class InputMethodManager {
*/
public static final int CONTROL_START_INITIAL = 1<<8;
+ /**
+ * Timeout in milliseconds for delivering a key to an IME.
+ */
+ static final long INPUT_METHOD_NOT_RESPONDING_TIMEOUT = 2500;
+
+ private static final int MAX_PENDING_EVENT_POOL_SIZE = 4;
+
final IInputMethodManager mService;
final Looper mMainLooper;
@@ -312,12 +320,17 @@ public final class InputMethodManager {
*/
IInputMethodSession mCurMethod;
+ PendingEvent mPendingEventPool;
+ int mPendingEventPoolSize;
+ PendingEvent mFirstPendingEvent;
+
// -----------------------------------------------------------
static final int MSG_DUMP = 1;
static final int MSG_BIND = 2;
static final int MSG_UNBIND = 3;
static final int MSG_SET_ACTIVE = 4;
+ static final int MSG_EVENT_TIMEOUT = 5;
class H extends Handler {
H(Looper looper) {
@@ -328,7 +341,7 @@ public final class InputMethodManager {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DUMP: {
- HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
+ SomeArgs args = (SomeArgs)msg.obj;
try {
doDump((FileDescriptor)args.arg1,
(PrintWriter)args.arg2, (String[])args.arg3);
@@ -338,10 +351,14 @@ public final class InputMethodManager {
synchronized (args.arg4) {
((CountDownLatch)args.arg4).countDown();
}
+ args.recycle();
return;
}
case MSG_BIND: {
final InputBindResult res = (InputBindResult)msg.obj;
+ if (DEBUG) {
+ Log.i(TAG, "handleMessage: MSG_BIND " + res.sequence + "," + res.id);
+ }
synchronized (mH) {
if (mBindSequence < 0 || mBindSequence != res.sequence) {
Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
@@ -358,6 +375,9 @@ public final class InputMethodManager {
}
case MSG_UNBIND: {
final int sequence = msg.arg1;
+ if (DEBUG) {
+ Log.i(TAG, "handleMessage: MSG_UNBIND " + sequence);
+ }
boolean startInput = false;
synchronized (mH) {
if (mBindSequence == sequence) {
@@ -390,6 +410,9 @@ public final class InputMethodManager {
}
case MSG_SET_ACTIVE: {
final boolean active = msg.arg1 != 0;
+ if (DEBUG) {
+ Log.i(TAG, "handleMessage: MSG_SET_ACTIVE " + active + ", was " + mActive);
+ }
synchronized (mH) {
mActive = active;
mFullscreenMode = false;
@@ -407,12 +430,32 @@ public final class InputMethodManager {
// Check focus again in case that "onWindowFocus" is called before
// handling this message.
if (mServedView != null && mServedView.hasWindowFocus()) {
- checkFocus(mHasBeenInactive);
+ // "finishComposingText" has been already called above. So we
+ // should not call mServedInputConnection.finishComposingText here.
+ // Also, please note that this handler thread could be different
+ // from a thread that created mServedView. That could happen
+ // the current activity is running in the system process.
+ // In that case, we really should not call
+ // mServedInputConnection.finishComposingText.
+ if (checkFocusNoStartInput(mHasBeenInactive, false)) {
+ startInputInner(null, 0, 0, 0);
+ }
}
}
}
return;
}
+ case MSG_EVENT_TIMEOUT: {
+ // Even though the message contains both the sequence number
+ // and the PendingEvent object itself, we only pass the
+ // sequence number to the timeoutEvent function because it's
+ // possible for the PendingEvent object to be dequeued and
+ // recycled concurrently. To avoid a possible race, we make
+ // a point of always looking up the PendingEvent within the
+ // queue given only the sequence number of the event.
+ timeoutEvent(msg.arg1);
+ return;
+ }
}
}
}
@@ -444,7 +487,7 @@ public final class InputMethodManager {
// interface to the system.
CountDownLatch latch = new CountDownLatch(1);
- HandlerCaller.SomeArgs sargs = new HandlerCaller.SomeArgs();
+ SomeArgs sargs = SomeArgs.obtain();
sargs.arg1 = fd;
sargs.arg2 = fout;
sargs.arg3 = args;
@@ -476,6 +519,18 @@ public final class InputMethodManager {
};
final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);
+
+ final IInputMethodCallback mInputMethodCallback = new IInputMethodCallback.Stub() {
+ @Override
+ public void finishedEvent(int seq, boolean handled) {
+ InputMethodManager.this.finishedEvent(seq, handled);
+ }
+
+ @Override
+ public void sessionCreated(IInputMethodSession session) {
+ // Stub -- not for use in the client.
+ }
+ };
InputMethodManager(IInputMethodManager service, Looper looper) {
mService = service;
@@ -1105,6 +1160,7 @@ public final class InputMethodManager {
if (res.id != null) {
mBindSequence = res.sequence;
mCurMethod = res.method;
+ mCurId = res.id;
} else if (mCurMethod == null) {
// This means there is no input method available.
if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
@@ -1194,20 +1250,16 @@ public final class InputMethodManager {
}
}
- private void checkFocus(boolean forceNewFocus) {
- if (checkFocusNoStartInput(forceNewFocus)) {
- startInputInner(null, 0, 0, 0);
- }
- }
-
/**
* @hide
*/
public void checkFocus() {
- checkFocus(false);
+ if (checkFocusNoStartInput(false, true)) {
+ startInputInner(null, 0, 0, 0);
+ }
}
- private boolean checkFocusNoStartInput(boolean forceNewFocus) {
+ private boolean checkFocusNoStartInput(boolean forceNewFocus, boolean finishComposingText) {
// This is called a lot, so short-circuit before locking.
if (mServedView == mNextServedView && !forceNewFocus) {
return false;
@@ -1241,7 +1293,7 @@ public final class InputMethodManager {
mServedConnecting = true;
}
- if (ic != null) {
+ if (finishComposingText && ic != null) {
ic.finishComposingText();
}
@@ -1286,7 +1338,7 @@ public final class InputMethodManager {
controlFlags |= CONTROL_WINDOW_FIRST;
}
- if (checkFocusNoStartInput(forceNewFocus)) {
+ if (checkFocusNoStartInput(forceNewFocus, true)) {
// We need to restart input on the current focus view. This
// should be done in conjunction with telling the system service
// about the window gaining focus, to help make the transition
@@ -1511,76 +1563,184 @@ public final class InputMethodManager {
* @hide
*/
public void dispatchKeyEvent(Context context, int seq, KeyEvent key,
- IInputMethodCallback callback) {
+ FinishedEventCallback callback) {
+ boolean handled = false;
synchronized (mH) {
if (DEBUG) Log.d(TAG, "dispatchKeyEvent");
-
- if (mCurMethod == null) {
- try {
- callback.finishedEvent(seq, false);
- } catch (RemoteException e) {
+
+ if (mCurMethod != null) {
+ if (key.getAction() == KeyEvent.ACTION_DOWN
+ && key.getKeyCode() == KeyEvent.KEYCODE_SYM) {
+ showInputMethodPickerLocked();
+ handled = true;
+ } else {
+ try {
+ if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod);
+ final long startTime = SystemClock.uptimeMillis();
+ enqueuePendingEventLocked(startTime, seq, mCurId, callback);
+ mCurMethod.dispatchKeyEvent(seq, key, mInputMethodCallback);
+ return;
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e);
+ }
}
- return;
}
-
- if (key.getAction() == KeyEvent.ACTION_DOWN
- && key.getKeyCode() == KeyEvent.KEYCODE_SYM) {
- showInputMethodPicker();
+ }
+
+ callback.finishedEvent(seq, handled);
+ }
+
+ /**
+ * @hide
+ */
+ public void dispatchTrackballEvent(Context context, int seq, MotionEvent motion,
+ FinishedEventCallback callback) {
+ synchronized (mH) {
+ if (DEBUG) Log.d(TAG, "dispatchTrackballEvent");
+
+ if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
try {
- callback.finishedEvent(seq, true);
+ if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod);
+ final long startTime = SystemClock.uptimeMillis();
+ enqueuePendingEventLocked(startTime, seq, mCurId, callback);
+ mCurMethod.dispatchTrackballEvent(seq, motion, mInputMethodCallback);
+ return;
} catch (RemoteException e) {
- }
- return;
- }
- try {
- if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod);
- mCurMethod.dispatchKeyEvent(seq, key, callback);
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e);
- try {
- callback.finishedEvent(seq, false);
- } catch (RemoteException ex) {
+ Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e);
}
}
}
+
+ callback.finishedEvent(seq, false);
}
/**
* @hide
*/
- void dispatchTrackballEvent(Context context, int seq, MotionEvent motion,
- IInputMethodCallback callback) {
+ public void dispatchGenericMotionEvent(Context context, int seq, MotionEvent motion,
+ FinishedEventCallback callback) {
synchronized (mH) {
- if (DEBUG) Log.d(TAG, "dispatchTrackballEvent");
-
- if (mCurMethod == null || mCurrentTextBoxAttribute == null) {
+ if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent");
+
+ if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
try {
- callback.finishedEvent(seq, false);
+ if (DEBUG) Log.v(TAG, "DISPATCH GENERIC MOTION: " + mCurMethod);
+ final long startTime = SystemClock.uptimeMillis();
+ enqueuePendingEventLocked(startTime, seq, mCurId, callback);
+ mCurMethod.dispatchGenericMotionEvent(seq, motion, mInputMethodCallback);
+ return;
} catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId + " dropping generic motion: " + motion, e);
}
- return;
}
-
- try {
- if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod);
- mCurMethod.dispatchTrackballEvent(seq, motion, callback);
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e);
- try {
- callback.finishedEvent(seq, false);
- } catch (RemoteException ex) {
- }
+ }
+
+ callback.finishedEvent(seq, false);
+ }
+
+ void finishedEvent(int seq, boolean handled) {
+ final FinishedEventCallback callback;
+ synchronized (mH) {
+ PendingEvent p = dequeuePendingEventLocked(seq);
+ if (p == null) {
+ return; // spurious, event already finished or timed out
}
+ mH.removeMessages(MSG_EVENT_TIMEOUT, p);
+ callback = p.mCallback;
+ recyclePendingEventLocked(p);
}
+ callback.finishedEvent(seq, handled);
}
- public void showInputMethodPicker() {
+ void timeoutEvent(int seq) {
+ final FinishedEventCallback callback;
synchronized (mH) {
- try {
- mService.showInputMethodPickerFromClient(mClient);
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId, e);
+ PendingEvent p = dequeuePendingEventLocked(seq);
+ if (p == null) {
+ return; // spurious, event already finished or timed out
}
+ long delay = SystemClock.uptimeMillis() - p.mStartTime;
+ Log.w(TAG, "Timeout waiting for IME to handle input event after "
+ + delay + "ms: " + p.mInputMethodId);
+ callback = p.mCallback;
+ recyclePendingEventLocked(p);
+ }
+ callback.finishedEvent(seq, false);
+ }
+
+ private void enqueuePendingEventLocked(
+ long startTime, int seq, String inputMethodId, FinishedEventCallback callback) {
+ PendingEvent p = obtainPendingEventLocked(startTime, seq, inputMethodId, callback);
+ p.mNext = mFirstPendingEvent;
+ mFirstPendingEvent = p;
+
+ Message msg = mH.obtainMessage(MSG_EVENT_TIMEOUT, seq, 0, p);
+ msg.setAsynchronous(true);
+ mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT);
+ }
+
+ private PendingEvent dequeuePendingEventLocked(int seq) {
+ PendingEvent p = mFirstPendingEvent;
+ if (p == null) {
+ return null;
+ }
+ if (p.mSeq == seq) {
+ mFirstPendingEvent = p.mNext;
+ } else {
+ PendingEvent prev;
+ do {
+ prev = p;
+ p = p.mNext;
+ if (p == null) {
+ return null;
+ }
+ } while (p.mSeq != seq);
+ prev.mNext = p.mNext;
+ }
+ p.mNext = null;
+ return p;
+ }
+
+ private PendingEvent obtainPendingEventLocked(
+ long startTime, int seq, String inputMethodId, FinishedEventCallback callback) {
+ PendingEvent p = mPendingEventPool;
+ if (p != null) {
+ mPendingEventPoolSize -= 1;
+ mPendingEventPool = p.mNext;
+ p.mNext = null;
+ } else {
+ p = new PendingEvent();
+ }
+
+ p.mStartTime = startTime;
+ p.mSeq = seq;
+ p.mInputMethodId = inputMethodId;
+ p.mCallback = callback;
+ return p;
+ }
+
+ private void recyclePendingEventLocked(PendingEvent p) {
+ p.mInputMethodId = null;
+ p.mCallback = null;
+
+ if (mPendingEventPoolSize < MAX_PENDING_EVENT_POOL_SIZE) {
+ mPendingEventPoolSize += 1;
+ p.mNext = mPendingEventPool;
+ mPendingEventPool = p;
+ }
+ }
+
+ public void showInputMethodPicker() {
+ synchronized (mH) {
+ showInputMethodPickerLocked();
+ }
+ }
+
+ private void showInputMethodPickerLocked() {
+ try {
+ mService.showInputMethodPickerFromClient(mClient);
+ } catch (RemoteException e) {
+ Log.w(TAG, "IME died: " + mCurId, e);
}
}
@@ -1773,4 +1933,22 @@ public final class InputMethodManager {
+ " mCursorCandStart=" + mCursorCandStart
+ " mCursorCandEnd=" + mCursorCandEnd);
}
+
+ /**
+ * Callback that is invoked when an input event that was dispatched to
+ * the IME has been finished.
+ * @hide
+ */
+ public interface FinishedEventCallback {
+ public void finishedEvent(int seq, boolean handled);
+ }
+
+ private static final class PendingEvent {
+ public PendingEvent mNext;
+
+ public long mStartTime;
+ public int mSeq;
+ public String mInputMethodId;
+ public FinishedEventCallback mCallback;
+ }
}
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index ea6f5ee..6386299 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -138,6 +138,21 @@ public interface InputMethodSession {
public void dispatchTrackballEvent(int seq, MotionEvent event, EventCallback callback);
/**
+ * This method is called when there is a generic motion event.
+ *
+ * <p>
+ * If the input method wants to handle this event, return true, otherwise
+ * return false and the caller (i.e. the application) will handle the event.
+ *
+ * @param event The motion event.
+ *
+ * @return Whether the input method wants to handle this event.
+ *
+ * @see android.view.MotionEvent
+ */
+ public void dispatchGenericMotionEvent(int seq, MotionEvent event, EventCallback callback);
+
+ /**
* Process a private command sent from the application to the input method.
* This can be used to provide domain-specific features that are
* only known between certain input methods and their clients.
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index b7c94a3..7895e6f 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -55,13 +55,14 @@ public final class InputMethodSubtype implements Parcelable {
private final int mSubtypeHashCode;
private final int mSubtypeIconResId;
private final int mSubtypeNameResId;
+ private final int mSubtypeId;
private final String mSubtypeLocale;
private final String mSubtypeMode;
private final String mSubtypeExtraValue;
private volatile HashMap<String, String> mExtraValueHashMapCache;
/**
- * Constructor.
+ * 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.
@@ -87,7 +88,7 @@ public final class InputMethodSubtype implements Parcelable {
}
/**
- * Constructor.
+ * 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.
@@ -112,6 +113,41 @@ public final class InputMethodSubtype implements Parcelable {
*/
public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
+ this(nameId, iconId, locale, mode, extraValue, isAuxiliary,
+ overridesImplicitlyEnabledSubtype, 0);
+ }
+
+ /**
+ * 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.
+ * @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.
+ * @param id The unique ID for the 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 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 : "";
@@ -119,8 +155,11 @@ public final class InputMethodSubtype implements Parcelable {
mSubtypeExtraValue = extraValue != null ? extraValue : "";
mIsAuxiliary = isAuxiliary;
mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
- mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
- mIsAuxiliary, mOverridesImplicitlyEnabledSubtype);
+ // 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;
}
InputMethodSubtype(Parcel source) {
@@ -135,8 +174,8 @@ public final class InputMethodSubtype implements Parcelable {
mSubtypeExtraValue = s != null ? s : "";
mIsAuxiliary = (source.readInt() == 1);
mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1);
- mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
- mIsAuxiliary, mOverridesImplicitlyEnabledSubtype);
+ mSubtypeHashCode = source.readInt();
+ mSubtypeId = source.readInt();
}
/**
@@ -288,6 +327,9 @@ public final class InputMethodSubtype implements Parcelable {
public boolean equals(Object o) {
if (o instanceof InputMethodSubtype) {
InputMethodSubtype subtype = (InputMethodSubtype) o;
+ if (subtype.mSubtypeId != 0 || mSubtypeId != 0) {
+ return (subtype.hashCode() == hashCode());
+ }
return (subtype.hashCode() == hashCode())
&& (subtype.getNameResId() == getNameResId())
&& (subtype.getMode().equals(getMode()))
@@ -313,6 +355,8 @@ public final class InputMethodSubtype implements Parcelable {
dest.writeString(mSubtypeExtraValue);
dest.writeInt(mIsAuxiliary ? 1 : 0);
dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0);
+ dest.writeInt(mSubtypeHashCode);
+ dest.writeInt(mSubtypeId);
}
public static final Parcelable.Creator<InputMethodSubtype> CREATOR
diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java
index 7dfb5bb..a51a8f6 100644
--- a/core/java/android/webkit/AccessibilityInjector.java
+++ b/core/java/android/webkit/AccessibilityInjector.java
@@ -34,6 +34,7 @@ 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;
@@ -53,7 +54,7 @@ class AccessibilityInjector {
private final WebView mWebView;
// The Java objects that are exposed to JavaScript.
- private TextToSpeech mTextToSpeech;
+ private TextToSpeechWrapper mTextToSpeech;
private CallbackHandler mCallback;
// Lazily loaded helper objects.
@@ -279,6 +280,7 @@ class AccessibilityInjector {
}
if (!shouldInjectJavaScript(url)) {
+ mAccessibilityScriptInjected = false;
toggleFallbackAccessibilityInjector(true);
return;
}
@@ -292,6 +294,23 @@ class AccessibilityInjector {
}
/**
+ * Adjusts the accessibility injection state to reflect changes in the
+ * JavaScript enabled state.
+ *
+ * @param enabled Whether JavaScript is enabled.
+ */
+ public void updateJavaScriptEnabled(boolean enabled) {
+ if (enabled) {
+ addAccessibilityApisIfNecessary();
+ } else {
+ removeAccessibilityApisIfNecessary();
+ }
+
+ // We have to reload the page after adding or removing APIs.
+ mWebView.reload();
+ }
+
+ /**
* Toggles the non-JavaScript method for handling accessibility.
*
* @param enabled {@code true} to enable the non-JavaScript method, or
@@ -349,10 +368,7 @@ class AccessibilityInjector {
if (mTextToSpeech != null) {
return;
}
-
- final String pkgName = mContext.getPackageName();
-
- mTextToSpeech = new TextToSpeech(mContext, null, null, pkgName + ".**webview**", true);
+ mTextToSpeech = new TextToSpeechWrapper(mContext);
mWebView.addJavascriptInterface(mTextToSpeech, ALIAS_TTS_JS_INTERFACE);
}
@@ -508,6 +524,41 @@ class AccessibilityInjector {
}
/**
+ * Used to protect the TextToSpeech class, only exposing the methods we want to expose.
+ */
+ private static class TextToSpeechWrapper {
+ private TextToSpeech mTextToSpeech;
+
+ public TextToSpeechWrapper(Context context) {
+ final String pkgName = context.getPackageName();
+ mTextToSpeech = new TextToSpeech(context, null, null, pkgName + ".**webview**", true);
+ }
+
+ @JavascriptInterface
+ @SuppressWarnings("unused")
+ public boolean isSpeaking() {
+ return mTextToSpeech.isSpeaking();
+ }
+
+ @JavascriptInterface
+ @SuppressWarnings("unused")
+ public int speak(String text, int queueMode, HashMap<String, String> params) {
+ return mTextToSpeech.speak(text, queueMode, params);
+ }
+
+ @JavascriptInterface
+ @SuppressWarnings("unused")
+ public int stop() {
+ return mTextToSpeech.stop();
+ }
+
+ @SuppressWarnings("unused")
+ protected void shutdown() {
+ mTextToSpeech.shutdown();
+ }
+ }
+
+ /**
* Exposes result interface to JavaScript.
*/
private static class CallbackHandler {
@@ -603,6 +654,7 @@ class AccessibilityInjector {
* @param id The result id of the request as a {@link String}.
* @param result The result of the request as a {@link String}.
*/
+ @JavascriptInterface
@SuppressWarnings("unused")
public void onResult(String id, String result) {
final long resultId;
diff --git a/core/java/android/webkit/BrowserDownloadListener.java b/core/java/android/webkit/BrowserDownloadListener.java
new file mode 100644
index 0000000..724cc62
--- /dev/null
+++ b/core/java/android/webkit/BrowserDownloadListener.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+/**
+ * An abstract download listener that allows passing extra information as
+ * part of onDownloadStart callback.
+ * @hide
+ */
+public abstract class BrowserDownloadListener implements DownloadListener {
+
+ /**
+ * Notify the host application that a file should be downloaded
+ * @param url The full url to the content that should be downloaded
+ * @param userAgent the user agent to be used for the download.
+ * @param contentDisposition Content-disposition http header, if
+ * present.
+ * @param mimetype The mimetype of the content reported by the server
+ * @param referer The referer associated with this url
+ * @param contentLength The file size reported by the server
+ */
+ public abstract void onDownloadStart(String url, String userAgent,
+ String contentDisposition, String mimetype, String referer,
+ long contentLength);
+
+
+ /**
+ * Notify the host application that a file should be downloaded
+ * @param url The full url to the content that should be downloaded
+ * @param userAgent the user agent to be used for the download.
+ * @param contentDisposition Content-disposition http header, if
+ * present.
+ * @param mimetype The mimetype of the content reported by the server
+ * @param contentLength The file size reported by the server
+ */
+ @Override
+ public void onDownloadStart(String url, String userAgent,
+ String contentDisposition, String mimetype, long contentLength) {
+
+ onDownloadStart(url, userAgent, contentDisposition, mimetype, null,
+ contentLength);
+ }
+}
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 5108990..4dbca23 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -74,6 +74,7 @@ class BrowserFrame extends Handler {
private final CallbackProxy mCallbackProxy;
private final WebSettingsClassic mSettings;
private final Context mContext;
+ private final WebViewDatabaseClassic mDatabase;
private final WebViewCore mWebViewCore;
/* package */ boolean mLoadInitFromJava;
private int mLoadType;
@@ -88,16 +89,24 @@ class BrowserFrame extends Handler {
// Is this frame the main frame?
private boolean mIsMainFrame;
+ // Javascript interface object
+ private class JSObject {
+ Object object;
+ boolean requireAnnotation;
+
+ public JSObject(Object object, boolean requireAnnotation) {
+ this.object = object;
+ this.requireAnnotation = requireAnnotation;
+ }
+ }
+
// Attached Javascript interfaces
- private Map<String, Object> mJavaScriptObjects;
+ private Map<String, JSObject> mJavaScriptObjects;
private Set<Object> mRemovedJavaScriptObjects;
// Key store handler when Chromium HTTP stack is used.
private KeyStoreHandler mKeyStoreHandler = null;
- // Implementation of the searchbox API.
- private final SearchBoxImpl mSearchBox;
-
// message ids
// a message posted when a frame loading is completed
static final int FRAME_COMPLETED = 1001;
@@ -233,20 +242,16 @@ class BrowserFrame extends Handler {
}
sConfigCallback.addHandler(this);
- mJavaScriptObjects = javascriptInterfaces;
- if (mJavaScriptObjects == null) {
- mJavaScriptObjects = new HashMap<String, Object>();
- }
+ mJavaScriptObjects = new HashMap<String, JSObject>();
+ addJavaScriptObjects(javascriptInterfaces);
mRemovedJavaScriptObjects = new HashSet<Object>();
mSettings = settings;
mContext = context;
mCallbackProxy = proxy;
+ mDatabase = WebViewDatabaseClassic.getInstance(appContext);
mWebViewCore = w;
- mSearchBox = new SearchBoxImpl(mWebViewCore, mCallbackProxy);
- mJavaScriptObjects.put(SearchBoxImpl.JS_INTERFACE_NAME, mSearchBox);
-
AssetManager am = context.getAssets();
nativeCreateFrame(w, am, proxy.getBackForwardList());
@@ -424,8 +429,7 @@ class BrowserFrame extends Handler {
if (h != null) {
String url = WebTextView.urlForAutoCompleteData(h.getUrl());
if (url != null) {
- WebViewDatabaseClassic.getInstance(mContext).setFormData(
- url, data);
+ mDatabase.setFormData(url, data);
}
}
}
@@ -497,9 +501,8 @@ class BrowserFrame extends Handler {
if (item != null) {
WebAddress uri = new WebAddress(item.getUrl());
String schemePlusHost = uri.getScheme() + uri.getHost();
- String[] up =
- WebViewDatabaseClassic.getInstance(mContext)
- .getUsernamePassword(schemePlusHost);
+ String[] up = mDatabase.getUsernamePassword(
+ schemePlusHost);
if (up != null && up[0] != null) {
setUsernamePassword(up[0], up[1]);
}
@@ -591,15 +594,34 @@ class BrowserFrame extends Handler {
Iterator<String> iter = mJavaScriptObjects.keySet().iterator();
while (iter.hasNext()) {
String interfaceName = iter.next();
- Object object = mJavaScriptObjects.get(interfaceName);
- if (object != null) {
+ JSObject jsobject = mJavaScriptObjects.get(interfaceName);
+ if (jsobject != null && jsobject.object != null) {
nativeAddJavascriptInterface(nativeFramePointer,
- mJavaScriptObjects.get(interfaceName), interfaceName);
+ jsobject.object, interfaceName, jsobject.requireAnnotation);
}
}
mRemovedJavaScriptObjects.clear();
+ }
- stringByEvaluatingJavaScriptFromString(SearchBoxImpl.JS_BRIDGE);
+ /*
+ * Add javascript objects to the internal list of objects. The default behavior
+ * is to allow access to inherited methods (no annotation needed). This is only
+ * used when js objects are passed through a constructor (via a hidden constructor).
+ *
+ * @TODO change the default behavior to be compatible with the public addjavascriptinterface
+ */
+ private void addJavaScriptObjects(Map<String, Object> javascriptInterfaces) {
+
+ // TODO in a separate CL provide logic to enable annotations for API level JB_MR1 and above.
+ if (javascriptInterfaces == null) return;
+ Iterator<String> iter = javascriptInterfaces.keySet().iterator();
+ while (iter.hasNext()) {
+ String interfaceName = iter.next();
+ Object object = javascriptInterfaces.get(interfaceName);
+ if (object != null) {
+ mJavaScriptObjects.put(interfaceName, new JSObject(object, false));
+ }
+ }
}
/**
@@ -619,11 +641,11 @@ class BrowserFrame extends Handler {
}
}
- public void addJavascriptInterface(Object obj, String interfaceName) {
+ public void addJavascriptInterface(Object obj, String interfaceName,
+ boolean requireAnnotation) {
assert obj != null;
removeJavascriptInterface(interfaceName);
-
- mJavaScriptObjects.put(interfaceName, obj);
+ mJavaScriptObjects.put(interfaceName, new JSObject(obj, requireAnnotation));
}
public void removeJavascriptInterface(String interfaceName) {
@@ -800,10 +822,10 @@ class BrowserFrame extends Handler {
// the post data (there could be another form on the
// page and that was posted instead.
String postString = new String(postData);
- WebViewDatabaseClassic db = WebViewDatabaseClassic.getInstance(mContext);
if (postString.contains(URLEncoder.encode(username)) &&
postString.contains(URLEncoder.encode(password))) {
- String[] saved = db.getUsernamePassword(schemePlusHost);
+ String[] saved = mDatabase.getUsernamePassword(
+ schemePlusHost);
if (saved != null) {
// null username implies that user has chosen not to
// save password
@@ -811,8 +833,7 @@ class BrowserFrame extends Handler {
// non-null username implies that user has
// chosen to save password, so update the
// recorded password
- db.setUsernamePassword(schemePlusHost, username,
- password);
+ mDatabase.setUsernamePassword(schemePlusHost, username, password);
}
} else {
// CallbackProxy will handle creating the resume
@@ -1004,7 +1025,7 @@ class BrowserFrame extends Handler {
}
private float density() {
- return mContext.getResources().getDisplayMetrics().density;
+ return WebViewCore.getFixedDisplayDensity(mContext);
}
/**
@@ -1138,7 +1159,7 @@ class BrowserFrame extends Handler {
* DownloadListener.
*/
private void downloadStart(String url, String userAgent,
- String contentDisposition, String mimeType, long contentLength) {
+ String contentDisposition, String mimeType, String referer, long contentLength) {
// This will only work if the url ends with the filename
if (mimeType.isEmpty()) {
try {
@@ -1158,7 +1179,7 @@ class BrowserFrame extends Handler {
mKeyStoreHandler = new KeyStoreHandler(mimeType);
} else {
mCallbackProxy.onDownloadStart(url, userAgent,
- contentDisposition, mimeType, contentLength);
+ contentDisposition, mimeType, referer, contentLength);
}
}
@@ -1191,10 +1212,6 @@ class BrowserFrame extends Handler {
}
}
- /*package*/ SearchBox getSearchBox() {
- return mSearchBox;
- }
-
/**
* Called by JNI when processing the X-Auto-Login header.
*/
@@ -1249,7 +1266,7 @@ class BrowserFrame extends Handler {
* Add a javascript interface to the main frame.
*/
private native void nativeAddJavascriptInterface(int nativeFramePointer,
- Object obj, String interfaceName);
+ Object obj, String interfaceName, boolean requireAnnotation);
public native void clearCache();
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index f0e6ff0..52f41e6 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -32,6 +32,7 @@ import java.util.Map;
/**
* Manages the HTTP cache used by an application's {@link WebView} instances.
* @deprecated Access to the HTTP cache will be removed in a future release.
+ * @hide Since {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
// The class CacheManager provides the persistent cache of content that is
// received over the network. The component handles parsing of HTTP headers and
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 2d9f60d..a326da2 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -71,7 +71,7 @@ class CallbackProxy extends Handler {
// Start with 100 to indicate it is not in load for the empty page.
private volatile int mLatestProgress = 100;
// Back/Forward list
- private final WebBackForwardList mBackForwardList;
+ private final WebBackForwardListClassic mBackForwardList;
// Back/Forward list client
private volatile WebBackForwardListClient mWebBackForwardListClient;
// Used to call startActivity during url override.
@@ -117,12 +117,8 @@ class CallbackProxy extends Handler {
private static final int ADD_HISTORY_ITEM = 135;
private static final int HISTORY_INDEX_CHANGED = 136;
private static final int AUTH_CREDENTIALS = 137;
- private static final int SET_INSTALLABLE_WEBAPP = 138;
- private static final int NOTIFY_SEARCHBOX_LISTENERS = 139;
private static final int AUTO_LOGIN = 140;
private static final int CLIENT_CERT_REQUEST = 141;
- private static final int SEARCHBOX_IS_SUPPORTED_CALLBACK = 142;
- private static final int SEARCHBOX_DISPATCH_COMPLETE_CALLBACK = 143;
private static final int PROCEEDED_AFTER_SSL_ERROR = 144;
// Message triggered by the client to resume execution
@@ -188,7 +184,7 @@ class CallbackProxy extends Handler {
// Used to start a default activity.
mContext = context;
mWebView = w;
- mBackForwardList = new WebBackForwardList(this);
+ mBackForwardList = new WebBackForwardListClassic(this);
}
protected synchronized void blockMessages() {
@@ -249,7 +245,7 @@ class CallbackProxy extends Handler {
* Get the Back/Forward list to return to the user or to update the cached
* history list.
*/
- public WebBackForwardList getBackForwardList() {
+ public WebBackForwardListClassic getBackForwardList() {
return mBackForwardList;
}
@@ -403,17 +399,18 @@ class CallbackProxy extends Handler {
break;
case PROCEEDED_AFTER_SSL_ERROR:
- if (mWebViewClient != null) {
- mWebViewClient.onProceededAfterSslError(mWebView.getWebView(),
+ if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) {
+ ((WebViewClientClassicExt) mWebViewClient).onProceededAfterSslError(
+ mWebView.getWebView(),
(SslError) msg.obj);
}
break;
case CLIENT_CERT_REQUEST:
- if (mWebViewClient != null) {
- HashMap<String, Object> map =
- (HashMap<String, Object>) msg.obj;
- mWebViewClient.onReceivedClientCertRequest(mWebView.getWebView(),
+ if (mWebViewClient != null && mWebViewClient instanceof WebViewClientClassicExt) {
+ HashMap<String, Object> map = (HashMap<String, Object>) msg.obj;
+ ((WebViewClientClassicExt) mWebViewClient).onReceivedClientCertRequest(
+ mWebView.getWebView(),
(ClientCertRequestHandler) map.get("handler"),
(String) map.get("host_and_port"));
}
@@ -452,10 +449,16 @@ class CallbackProxy extends Handler {
String contentDisposition =
msg.getData().getString("contentDisposition");
String mimetype = msg.getData().getString("mimetype");
+ String referer = msg.getData().getString("referer");
Long contentLength = msg.getData().getLong("contentLength");
- mDownloadListener.onDownloadStart(url, userAgent,
- contentDisposition, mimetype, contentLength);
+ if (mDownloadListener instanceof BrowserDownloadListener) {
+ ((BrowserDownloadListener) mDownloadListener).onDownloadStart(url,
+ userAgent, contentDisposition, mimetype, referer, contentLength);
+ } else {
+ mDownloadListener.onDownloadStart(url, userAgent,
+ contentDisposition, mimetype, contentLength);
+ }
}
break;
@@ -736,6 +739,14 @@ class CallbackProxy extends Handler {
res.cancel();
}
})
+ .setOnCancelListener(
+ new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(
+ DialogInterface dialog) {
+ res.cancel();
+ }
+ })
.show();
}
receiver.setReady();
@@ -857,19 +868,6 @@ class CallbackProxy extends Handler {
host, realm, username, password);
break;
}
- case SET_INSTALLABLE_WEBAPP:
- if (mWebChromeClient != null) {
- mWebChromeClient.setInstallableWebApp();
- }
- break;
- case NOTIFY_SEARCHBOX_LISTENERS: {
- SearchBoxImpl searchBox = (SearchBoxImpl) mWebView.getSearchBox();
-
- @SuppressWarnings("unchecked")
- List<String> suggestions = (List<String>) msg.obj;
- searchBox.handleSuggestions(msg.getData().getString("query"), suggestions);
- break;
- }
case AUTO_LOGIN: {
if (mWebViewClient != null) {
String realm = msg.getData().getString("realm");
@@ -880,19 +878,6 @@ class CallbackProxy extends Handler {
}
break;
}
- case SEARCHBOX_IS_SUPPORTED_CALLBACK: {
- SearchBoxImpl searchBox = (SearchBoxImpl) mWebView.getSearchBox();
- Boolean supported = (Boolean) msg.obj;
- searchBox.handleIsSupportedCallback(supported);
- break;
- }
- case SEARCHBOX_DISPATCH_COMPLETE_CALLBACK: {
- SearchBoxImpl searchBox = (SearchBoxImpl) mWebView.getSearchBox();
- Boolean success = (Boolean) msg.obj;
- searchBox.handleDispatchCompleteCallback(msg.getData().getString("function"),
- msg.getData().getInt("id"), success);
- break;
- }
}
}
@@ -1081,7 +1066,7 @@ class CallbackProxy extends Handler {
}
public void onProceededAfterSslError(SslError error) {
- if (mWebViewClient == null) {
+ if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) {
return;
}
Message msg = obtainMessage(PROCEEDED_AFTER_SSL_ERROR);
@@ -1092,7 +1077,7 @@ class CallbackProxy extends Handler {
public void onReceivedClientCertRequest(ClientCertRequestHandler handler, String host_and_port) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
- if (mWebViewClient == null) {
+ if (mWebViewClient == null || !(mWebViewClient instanceof WebViewClientClassicExt)) {
handler.cancel();
return;
}
@@ -1176,7 +1161,8 @@ class CallbackProxy extends Handler {
* return false.
*/
public boolean onDownloadStart(String url, String userAgent,
- String contentDisposition, String mimetype, long contentLength) {
+ String contentDisposition, String mimetype, String referer,
+ long contentLength) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
if (mDownloadListener == null) {
@@ -1189,6 +1175,7 @@ class CallbackProxy extends Handler {
bundle.putString("url", url);
bundle.putString("userAgent", userAgent);
bundle.putString("mimetype", mimetype);
+ bundle.putString("referer", referer);
bundle.putLong("contentLength", contentLength);
bundle.putString("contentDisposition", contentDisposition);
sendMessage(msg);
@@ -1301,7 +1288,7 @@ class CallbackProxy extends Handler {
public void onReceivedIcon(Bitmap icon) {
// The current item might be null if the icon was already stored in the
// database and this is a new WebView.
- WebHistoryItem i = mBackForwardList.getCurrentItem();
+ WebHistoryItemClassic i = mBackForwardList.getCurrentItem();
if (i != null) {
i.setFavicon(icon);
}
@@ -1316,7 +1303,7 @@ class CallbackProxy extends Handler {
/* package */ void onReceivedTouchIconUrl(String url, boolean precomposed) {
// We should have a current item but we do not want to crash so check
// for null.
- WebHistoryItem i = mBackForwardList.getCurrentItem();
+ WebHistoryItemClassic i = mBackForwardList.getCurrentItem();
if (i != null) {
i.setTouchIconUrl(url, precomposed);
}
@@ -1608,13 +1595,6 @@ class CallbackProxy extends Handler {
sendMessage(msg);
}
- void setInstallableWebApp() {
- if (mWebChromeClient == null) {
- return;
- }
- sendMessage(obtainMessage(SET_INSTALLABLE_WEBAPP));
- }
-
boolean canShowAlertDialog() {
// We can only display the alert dialog if mContext is
// an Activity context.
@@ -1625,29 +1605,6 @@ class CallbackProxy extends Handler {
return mContext instanceof Activity;
}
- void onSearchboxSuggestionsReceived(String query, List<String> suggestions) {
- Message msg = obtainMessage(NOTIFY_SEARCHBOX_LISTENERS);
- msg.obj = suggestions;
- msg.getData().putString("query", query);
-
- sendMessage(msg);
- }
-
- void onIsSupportedCallback(boolean isSupported) {
- Message msg = obtainMessage(SEARCHBOX_IS_SUPPORTED_CALLBACK);
- msg.obj = Boolean.valueOf(isSupported);
- sendMessage(msg);
- }
-
- void onSearchboxDispatchCompleteCallback(String function, int id, boolean success) {
- Message msg = obtainMessage(SEARCHBOX_DISPATCH_COMPLETE_CALLBACK);
- msg.obj = Boolean.valueOf(success);
- msg.getData().putString("function", function);
- msg.getData().putInt("id", id);
-
- sendMessage(msg);
- }
-
private synchronized void sendMessageToUiThreadSync(Message msg) {
sendMessage(msg);
WebCoreThreadWatchdog.pause();
diff --git a/core/java/android/webkit/CertTool.java b/core/java/android/webkit/CertTool.java
index a2325c3..e4d09a9 100644
--- a/core/java/android/webkit/CertTool.java
+++ b/core/java/android/webkit/CertTool.java
@@ -16,6 +16,7 @@
package android.webkit;
+import com.android.org.bouncycastle.asn1.ASN1Encoding;
import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import com.android.org.bouncycastle.jce.netscape.NetscapeCertRequest;
@@ -57,7 +58,7 @@ final class CertTool {
NetscapeCertRequest request = new NetscapeCertRequest(challenge,
MD5_WITH_RSA, pair.getPublic());
request.sign(pair.getPrivate());
- byte[] signed = request.toASN1Object().getDEREncoded();
+ byte[] signed = request.toASN1Primitive().getEncoded(ASN1Encoding.DER);
Credentials.getInstance().install(context, pair);
return new String(Base64.encode(signed));
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 30c713e..2b75d83 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -37,8 +37,8 @@ public class CookieManager {
/**
* Gets the singleton CookieManager instance. If this method is used
* before the application instantiates a {@link WebView} instance,
- * {@link CookieSyncManager#createInstance(Context)} must be called
- * first.
+ * {@link CookieSyncManager#createInstance CookieSyncManager.createInstance(Context)}
+ * must be called first.
*
* @return the singleton CookieManager instance
*/
diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java
index 4e99335..276bcae 100644
--- a/core/java/android/webkit/CookieSyncManager.java
+++ b/core/java/android/webkit/CookieSyncManager.java
@@ -86,10 +86,8 @@ public final class CookieSyncManager extends WebSyncManager {
throw new IllegalArgumentException("Invalid context argument");
}
- JniUtil.setContext(context);
- Context appContext = context.getApplicationContext();
if (sRef == null) {
- sRef = new CookieSyncManager(appContext);
+ sRef = new CookieSyncManager(context);
}
return sRef;
}
diff --git a/core/java/android/webkit/DateSorter.java b/core/java/android/webkit/DateSorter.java
index 0e8ad7e..82c13ae 100644
--- a/core/java/android/webkit/DateSorter.java
+++ b/core/java/android/webkit/DateSorter.java
@@ -21,6 +21,9 @@ import android.content.res.Resources;
import java.util.Calendar;
import java.util.Date;
+import java.util.Locale;
+
+import libcore.icu.LocaleData;
/**
* Sorts dates into the following groups:
@@ -63,8 +66,13 @@ public class DateSorter {
mBins[3] = c.getTimeInMillis(); // One month ago
// build labels
- mLabels[0] = context.getText(com.android.internal.R.string.today).toString();
- mLabels[1] = context.getText(com.android.internal.R.string.yesterday).toString();
+ Locale locale = resources.getConfiguration().locale;
+ if (locale == null) {
+ locale = Locale.getDefault();
+ }
+ LocaleData localeData = LocaleData.get(locale);
+ mLabels[0] = localeData.today;
+ mLabels[1] = localeData.yesterday;
int resId = com.android.internal.R.plurals.last_num_days;
String format = resources.getQuantityString(resId, NUM_DAYS_AGO);
diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java
index 2e8656c..a4d240d 100755
--- a/core/java/android/webkit/DeviceOrientationService.java
+++ b/core/java/android/webkit/DeviceOrientationService.java
@@ -123,7 +123,7 @@ final class DeviceOrientationService implements SensorEventListener {
// The angles are in radians
float[] rotationAngles = new float[3];
SensorManager.getOrientation(deviceRotationMatrix, rotationAngles);
- double alpha = Math.toDegrees(-rotationAngles[0]) - 90.0;
+ double alpha = Math.toDegrees(-rotationAngles[0]);
while (alpha < 0.0) { alpha += 360.0; } // [0, 360)
double beta = Math.toDegrees(-rotationAngles[1]);
while (beta < -180.0) { beta += 360.0; } // [-180, 180)
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
index 33eaad6..9b93805 100644
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ b/core/java/android/webkit/HTML5VideoFullScreen.java
@@ -1,3 +1,18 @@
+/*
+ * 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;
@@ -75,9 +90,10 @@ public class HTML5VideoFullScreen extends HTML5VideoView
// ratio is correct.
private int mVideoWidth;
private int mVideoHeight;
-
+ private boolean mPlayingWhenDestroyed = false;
SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
{
+ @Override
public void surfaceChanged(SurfaceHolder holder, int format,
int w, int h)
{
@@ -91,6 +107,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView
}
}
+ @Override
public void surfaceCreated(SurfaceHolder holder)
{
mSurfaceHolder = holder;
@@ -99,14 +116,14 @@ public class HTML5VideoFullScreen extends HTML5VideoView
prepareForFullScreen();
}
+ @Override
public void surfaceDestroyed(SurfaceHolder holder)
{
- // After we return from this we can't use the surface any more.
- // The current Video View will be destroy when we play a new video.
+ mPlayingWhenDestroyed = mPlayer.isPlaying();
pauseAndDispatch(mProxy);
- // TODO: handle full screen->inline mode transition without a reload.
- mPlayer.release();
- mPlayer = null;
+ // We need to set the display to null before switching into inline
+ // mode to avoid error.
+ mPlayer.setDisplay(null);
mSurfaceHolder = null;
if (mMediaController != null) {
mMediaController.hide();
@@ -194,18 +211,6 @@ public class HTML5VideoFullScreen extends HTML5VideoView
mCanPause = mCanSeekBack = mCanSeekForward = true;
}
- if (mProgressView != null) {
- mProgressView.setVisibility(View.GONE);
- }
-
- mVideoWidth = mp.getVideoWidth();
- mVideoHeight = mp.getVideoHeight();
- // This will trigger the onMeasure to get the display size right.
- mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
- // Call into the native to ask for the state, if still in play mode,
- // this will trigger the video to play.
- mProxy.dispatchOnRestoreState();
-
if (getStartWhenPrepared()) {
mPlayer.start();
// Clear the flag.
@@ -219,20 +224,31 @@ public class HTML5VideoFullScreen extends HTML5VideoView
mMediaController.setEnabled(true);
mMediaController.show();
}
+
+ if (mProgressView != null) {
+ mProgressView.setVisibility(View.GONE);
+ }
+
+ mVideoWidth = mp.getVideoWidth();
+ mVideoHeight = mp.getVideoHeight();
+ // This will trigger the onMeasure to get the display size right.
+ mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
+
}
+ @Override
public boolean fullScreenExited() {
return (mLayout == null);
}
private final WebChromeClient.CustomViewCallback mCallback =
new WebChromeClient.CustomViewCallback() {
+ @Override
public void onCustomViewHidden() {
// It listens to SurfaceHolder.Callback.SurfaceDestroyed event
// which happens when the video view is detached from its parent
// view. This happens in the WebChromeClient before this method
// is invoked.
- mProxy.dispatchOnStopFullScreen();
mLayout.removeView(getSurfaceView());
if (mProgressView != null) {
@@ -242,12 +258,11 @@ public class HTML5VideoFullScreen extends HTML5VideoView
mLayout = null;
// Re enable plugin views.
mProxy.getWebView().getViewManager().showAll();
-
- mProxy = null;
-
// Don't show the controller after exiting the full screen.
mMediaController = null;
- mCurrentState = STATE_RESETTED;
+ // Continue the inline mode playing if necessary.
+ mProxy.dispatchOnStopFullScreen(mPlayingWhenDestroyed);
+ mProxy = null;
}
};
@@ -264,7 +279,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView
mVideoSurfaceView.setFocusable(true);
mVideoSurfaceView.setFocusableInTouchMode(true);
mVideoSurfaceView.requestFocus();
-
+ mVideoSurfaceView.setOnKeyListener(mProxy);
// Create a FrameLayout that will contain the VideoView and the
// progress view (if any).
mLayout = new FrameLayout(mProxy.getContext());
@@ -296,6 +311,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView
* @return true when we are in full screen mode, even the surface not fully
* created.
*/
+ @Override
public boolean isFullScreenMode() {
return true;
}
@@ -334,6 +350,7 @@ public class HTML5VideoFullScreen extends HTML5VideoView
// Other listeners functions:
private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
new MediaPlayer.OnBufferingUpdateListener() {
+ @Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
mCurrentBufferPercentage = percent;
}
diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java
index 2c7ea5d..2ab2ab9 100644
--- a/core/java/android/webkit/HTML5VideoInline.java
+++ b/core/java/android/webkit/HTML5VideoInline.java
@@ -1,10 +1,24 @@
+/*
+ * 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.Manifest.permission;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
-import android.media.MediaPlayer;
import android.webkit.HTML5VideoView;
import android.webkit.HTML5VideoViewProxy;
import android.view.Surface;
@@ -34,8 +48,8 @@ public class HTML5VideoInline extends HTML5VideoView{
}
}
- HTML5VideoInline(int videoLayerId, int position) {
- init(videoLayerId, position, false);
+ HTML5VideoInline(int videoLayerId, int position, boolean skipPrepare) {
+ init(videoLayerId, position, skipPrepare);
}
@Override
@@ -84,7 +98,7 @@ public class HTML5VideoInline extends HTML5VideoView{
return mSurfaceTexture;
}
- public boolean surfaceTextureDeleted() {
+ public static boolean surfaceTextureDeleted() {
return (mSurfaceTexture == null);
}
@@ -110,7 +124,9 @@ public class HTML5VideoInline extends HTML5VideoView{
}
private void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) {
- mSurfaceTexture.setOnFrameAvailableListener(l);
+ if (mSurfaceTexture != null) {
+ mSurfaceTexture.setOnFrameAvailableListener(l);
+ }
}
}
diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java
index 371feea..0e8a5db 100644
--- a/core/java/android/webkit/HTML5VideoView.java
+++ b/core/java/android/webkit/HTML5VideoView.java
@@ -1,11 +1,23 @@
+/*
+ * 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.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.net.Uri;
-import android.util.Log;
-import android.view.SurfaceView;
import android.webkit.HTML5VideoViewProxy;
import java.io.IOException;
import java.util.HashMap;
@@ -35,6 +47,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener {
static final int STATE_PREPARED = 2;
static final int STATE_PLAYING = 3;
static final int STATE_RESETTED = 4;
+ static final int STATE_RELEASED = 5;
protected HTML5VideoViewProxy mProxy;
@@ -126,7 +139,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener {
}
public void reset() {
- if (mCurrentState != STATE_RESETTED) {
+ if (mCurrentState < STATE_RESETTED) {
mPlayer.reset();
}
mCurrentState = STATE_RESETTED;
@@ -138,6 +151,18 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener {
}
}
+ public static void release() {
+ if (mPlayer != null && mCurrentState != STATE_RELEASED) {
+ mPlayer.release();
+ mPlayer = null;
+ }
+ mCurrentState = STATE_RELEASED;
+ }
+
+ public boolean isReleased() {
+ return mCurrentState == STATE_RELEASED;
+ }
+
public boolean getPauseDuringPreparing() {
return mPauseDuringPreparing;
}
@@ -337,11 +362,6 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener {
// Only used in HTML5VideoFullScreen
}
- public boolean surfaceTextureDeleted() {
- // Only meaningful for HTML5VideoInline
- return false;
- }
-
public boolean fullScreenExited() {
// Only meaningful for HTML5VideoFullScreen
return false;
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index ab884df..a3d62ae 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -31,6 +31,8 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -47,7 +49,8 @@ class HTML5VideoViewProxy extends Handler
MediaPlayer.OnCompletionListener,
MediaPlayer.OnErrorListener,
MediaPlayer.OnInfoListener,
- SurfaceTexture.OnFrameAvailableListener {
+ SurfaceTexture.OnFrameAvailableListener,
+ View.OnKeyListener {
// Logging tag.
private static final String LOGTAG = "HTML5VideoViewProxy";
@@ -59,6 +62,7 @@ class HTML5VideoViewProxy extends Handler
private static final int LOAD_DEFAULT_POSTER = 104;
private static final int BUFFERING_START = 105;
private static final int BUFFERING_END = 106;
+ private static final int ENTER_FULLSCREEN = 107;
// Message Ids to be handled on the WebCore thread
private static final int PREPARED = 200;
@@ -93,9 +97,6 @@ class HTML5VideoViewProxy extends Handler
private static HTML5VideoView mHTML5VideoView;
private static boolean isVideoSelfEnded = false;
- // By using the baseLayer and the current video Layer ID, we can
- // identify the exact layer on the UI thread to use the SurfaceTexture.
- private static int mBaseLayer = 0;
private static void setPlayerBuffering(boolean playerBuffering) {
mHTML5VideoView.setPlayerBuffering(playerBuffering);
@@ -108,9 +109,7 @@ class HTML5VideoViewProxy extends Handler
// Don't do this for full screen mode.
if (mHTML5VideoView != null
&& !mHTML5VideoView.isFullScreenMode()
- && !mHTML5VideoView.surfaceTextureDeleted()) {
- mBaseLayer = layer;
-
+ && !mHTML5VideoView.isReleased()) {
int currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
SurfaceTexture surfTexture =
HTML5VideoInline.getSurfaceTexture(currentVideoLayerId);
@@ -126,7 +125,6 @@ class HTML5VideoViewProxy extends Handler
if (playerState >= HTML5VideoView.STATE_PREPARED
&& !foundInTree) {
mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
- mHTML5VideoView.deleteSurfaceTexture();
}
}
}
@@ -136,9 +134,6 @@ class HTML5VideoViewProxy extends Handler
public static void pauseAndDispatch() {
if (mHTML5VideoView != null) {
mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
- // When switching out, clean the video content on the old page
- // by telling the layer not readyToUseSurfTex.
- setBaseLayer(mBaseLayer);
}
}
@@ -217,9 +212,16 @@ class HTML5VideoViewProxy extends Handler
}
}
+ boolean skipPrepare = false;
+ boolean createInlineView = false;
if (backFromFullScreenMode
+ && currentVideoLayerId == videoLayerId
+ && !mHTML5VideoView.isReleased()) {
+ skipPrepare = true;
+ createInlineView = true;
+ } else if(backFromFullScreenMode
|| currentVideoLayerId != videoLayerId
- || mHTML5VideoView.surfaceTextureDeleted()) {
+ || HTML5VideoInline.surfaceTextureDeleted()) {
// Here, we handle the case when switching to a new video,
// either inside a WebView or across WebViews
// For switching videos within a WebView or across the WebView,
@@ -231,12 +233,18 @@ class HTML5VideoViewProxy extends Handler
}
mHTML5VideoView.reset();
}
+ createInlineView = true;
+ }
+ if (createInlineView) {
mCurrentProxy = proxy;
- mHTML5VideoView = new HTML5VideoInline(videoLayerId, time);
+ mHTML5VideoView = new HTML5VideoInline(videoLayerId, time, skipPrepare);
mHTML5VideoView.setVideoURI(url, mCurrentProxy);
mHTML5VideoView.prepareDataAndDisplayMode(proxy);
- } else if (mCurrentProxy == proxy) {
+ return;
+ }
+
+ if (mCurrentProxy == proxy) {
// Here, we handle the case when we keep playing with one video
if (!mHTML5VideoView.isPlaying()) {
mHTML5VideoView.seekTo(time);
@@ -278,9 +286,6 @@ class HTML5VideoViewProxy extends Handler
if (!mHTML5VideoView.isFullScreenMode()) {
mHTML5VideoView.start();
}
- if (mBaseLayer != 0) {
- setBaseLayer(mBaseLayer);
- }
}
public static void end() {
@@ -297,6 +302,7 @@ class HTML5VideoViewProxy extends Handler
// A bunch event listeners for our VideoView
// MediaPlayer.OnPreparedListener
+ @Override
public void onPrepared(MediaPlayer mp) {
VideoPlayer.onPrepared();
Message msg = Message.obtain(mWebCoreHandler, PREPARED);
@@ -309,6 +315,7 @@ class HTML5VideoViewProxy extends Handler
}
// MediaPlayer.OnCompletionListener;
+ @Override
public void onCompletion(MediaPlayer mp) {
// The video ended by itself, so we need to
// send a message to the UI thread to dismiss
@@ -318,6 +325,7 @@ class HTML5VideoViewProxy extends Handler
}
// MediaPlayer.OnErrorListener
+ @Override
public boolean onError(MediaPlayer mp, int what, int extra) {
sendMessage(obtainMessage(ERROR));
return false;
@@ -333,8 +341,9 @@ class HTML5VideoViewProxy extends Handler
mWebCoreHandler.sendMessage(msg);
}
- public void dispatchOnStopFullScreen() {
+ public void dispatchOnStopFullScreen(boolean stillPlaying) {
Message msg = Message.obtain(mWebCoreHandler, STOPFULLSCREEN);
+ msg.arg1 = stillPlaying ? 1 : 0;
mWebCoreHandler.sendMessage(msg);
}
@@ -369,6 +378,15 @@ class HTML5VideoViewProxy extends Handler
}
break;
}
+ case ENTER_FULLSCREEN:{
+ String url = (String) msg.obj;
+ WebChromeClient client = mWebView.getWebChromeClient();
+ int videoLayerID = msg.arg1;
+ if (client != null) {
+ VideoPlayer.enterFullScreenVideo(videoLayerID, url, this, mWebView);
+ }
+ break;
+ }
case SEEK: {
Integer time = (Integer) msg.obj;
mSeekPosition = time;
@@ -473,6 +491,7 @@ class HTML5VideoViewProxy extends Handler
releaseQueue();
}
// EventHandler methods. Executed on the network thread.
+ @Override
public void status(int major_version,
int minor_version,
int code,
@@ -480,10 +499,12 @@ class HTML5VideoViewProxy extends Handler
mStatusCode = code;
}
+ @Override
public void headers(Headers headers) {
mHeaders = headers;
}
+ @Override
public void data(byte[] data, int len) {
if (mPosterBytes == null) {
mPosterBytes = new ByteArrayOutputStream();
@@ -491,6 +512,7 @@ class HTML5VideoViewProxy extends Handler
mPosterBytes.write(data, 0, len);
}
+ @Override
public void endData() {
if (mStatusCode == 200) {
if (mPosterBytes.size() > 0) {
@@ -508,6 +530,7 @@ class HTML5VideoViewProxy extends Handler
}
if (mUrl != null) {
mHandler.post(new Runnable() {
+ @Override
public void run() {
if (mRequestHandle != null) {
mRequestHandle.setupRedirect(mUrl.toString(), mStatusCode,
@@ -519,14 +542,17 @@ class HTML5VideoViewProxy extends Handler
}
}
+ @Override
public void certificate(SslCertificate certificate) {
// Don't care.
}
+ @Override
public void error(int id, String description) {
cleanup();
}
+ @Override
public boolean handleSslErrorRequest(SslError error) {
// Don't care. If this happens, data() will never be called so
// mPosterBytes will never be created, so no need to call cleanup.
@@ -613,7 +639,7 @@ class HTML5VideoViewProxy extends Handler
nativeOnTimeupdate(msg.arg1, mNativePointer);
break;
case STOPFULLSCREEN:
- nativeOnStopFullscreen(mNativePointer);
+ nativeOnStopFullscreen(msg.arg1, mNativePointer);
break;
case RESTORESTATE:
nativeOnRestoreState(mNativePointer);
@@ -664,6 +690,21 @@ class HTML5VideoViewProxy extends Handler
}
/**
+ * Play a video stream in full screen mode.
+ * @param url is the URL of the video stream.
+ */
+ public void enterFullscreenForVideoLayer(String url, int videoLayerID) {
+ if (url == null) {
+ return;
+ }
+
+ Message message = obtainMessage(ENTER_FULLSCREEN);
+ message.arg1 = videoLayerID;
+ message.obj = url;
+ sendMessage(message);
+ }
+
+ /**
* Seek into the video stream.
* @param time is the position in the video stream.
*/
@@ -748,7 +789,7 @@ class HTML5VideoViewProxy extends Handler
private native void nativeOnPaused(int nativePointer);
private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
private native void nativeOnTimeupdate(int position, int nativePointer);
- private native void nativeOnStopFullscreen(int nativePointer);
+ private native void nativeOnStopFullscreen(int stillPlaying, int nativePointer);
private native void nativeOnRestoreState(int nativePointer);
private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture,
int baseLayer, int videoLayerId, int textureName,
@@ -763,4 +804,17 @@ class HTML5VideoViewProxy extends Handler
}
return false;
}
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ return true;
+ } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled()) {
+ VideoPlayer.exitFullScreenVideo(this, mWebView);
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java
index 2fbd1d0..296d960 100644
--- a/core/java/android/webkit/HttpAuthHandler.java
+++ b/core/java/android/webkit/HttpAuthHandler.java
@@ -19,40 +19,51 @@ package android.webkit;
import android.os.Handler;
/**
- * HTTP authentication request that must be handled by the user interface.
- * WebView creates the object and hands it to the current {@link WebViewClient},
- * which must call either {@link #proceed(String, String)} or {@link #cancel()}.
+ * Represents a request for HTTP authentication. Instances of this class are
+ * created by the WebView and passed to
+ * {@link WebViewClient#onReceivedHttpAuthRequest}. The host application must
+ * call either {@link #proceed} or {@link #cancel} to set the WebView's
+ * response to the request.
*/
public class HttpAuthHandler extends Handler {
/**
- * Package-private constructor needed for API compatibility.
+ * @hide Only for use by WebViewProvider implementations.
*/
- HttpAuthHandler() {
+ public HttpAuthHandler() {
}
/**
- * @return True if we can use user credentials on record
- * (ie, if we did not fail trying to use them last time)
+ * Gets whether the credentials stored for the current host (i.e. the host
+ * for which {@link WebViewClient#onReceivedHttpAuthRequest} was called)
+ * are suitable for use. Credentials are not suitable if they have
+ * previously been rejected by the server for the current request.
+ *
+ * @return whether the credentials are suitable for use
+ * @see Webview#getHttpAuthUsernamePassword
*/
public boolean useHttpAuthUsernamePassword() {
return false;
}
/**
- * Cancel the authorization request.
+ * Instructs the WebView to cancel the authentication request.
*/
public void cancel() {
}
/**
- * Proceed with the authorization with the given credentials.
+ * Instructs the WebView to proceed with the authentication with the given
+ * credentials. Credentials for use with this method can be retrieved from
+ * the WebView's store using {@link WebView#getHttpAuthUsernamePassword}.
*/
public void proceed(String username, String password) {
}
/**
- * return true if the prompt dialog should be suppressed.
+ * Gets whether the prompt dialog should be suppressed.
+ *
+ * @return whether the prompt dialog should be suppressed
* @hide
*/
public boolean suppressDialog() {
diff --git a/core/java/android/webkit/JavascriptInterface.java b/core/java/android/webkit/JavascriptInterface.java
new file mode 100644
index 0000000..6cd2a7b
--- /dev/null
+++ b/core/java/android/webkit/JavascriptInterface.java
@@ -0,0 +1,35 @@
+/*
+ * 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that allows exposing methods to JavaScript. Starting from API level
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} and above, only methods explicitly
+ * marked with this annotation are available to the Javascript code. See
+ * {@link android.webkit.WebView#addJavascriptInterface} for more information about it.
+ *
+ */
+@SuppressWarnings("javadoc")
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface JavascriptInterface {
+} \ No newline at end of file
diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java
index e3e6092..01a81c4 100644
--- a/core/java/android/webkit/JniUtil.java
+++ b/core/java/android/webkit/JniUtil.java
@@ -173,8 +173,8 @@ class JniUtil {
checkInitialized();
// If the device has not checked in it won't have pulled down the system setting for the
// Autofill Url. In that case we will not make autofill server requests.
- return Settings.Secure.getString(sContext.getContentResolver(),
- Settings.Secure.WEB_AUTOFILL_QUERY_URL);
+ return Settings.Global.getString(sContext.getContentResolver(),
+ Settings.Global.WEB_AUTOFILL_QUERY_URL);
}
private static boolean canSatisfyMemoryAllocation(long bytesRequested) {
diff --git a/core/java/android/webkit/MockGeolocation.java b/core/java/android/webkit/MockGeolocation.java
index fbda492..885c6c2 100644
--- a/core/java/android/webkit/MockGeolocation.java
+++ b/core/java/android/webkit/MockGeolocation.java
@@ -17,21 +17,29 @@
package android.webkit;
/**
- * This class is simply a container for the methods used to configure WebKit's
- * mock Geolocation service for use in LayoutTests.
+ * Used to configure the mock Geolocation client for the LayoutTests.
* @hide
*/
public final class MockGeolocation {
+ private WebViewCore mWebViewCore;
- // Global instance of a MockGeolocation
- private static MockGeolocation sMockGeolocation;
+ public MockGeolocation(WebViewCore webViewCore) {
+ mWebViewCore = webViewCore;
+ }
+
+ /**
+ * Sets use of the mock Geolocation client. Also resets that client.
+ */
+ public void setUseMock() {
+ nativeSetUseMock(mWebViewCore);
+ }
/**
* Set the position for the mock Geolocation service.
*/
public void setPosition(double latitude, double longitude, double accuracy) {
// This should only ever be called on the WebKit thread.
- nativeSetPosition(latitude, longitude, accuracy);
+ nativeSetPosition(mWebViewCore, latitude, longitude, accuracy);
}
/**
@@ -39,21 +47,18 @@ public final class MockGeolocation {
*/
public void setError(int code, String message) {
// This should only ever be called on the WebKit thread.
- nativeSetError(code, message);
+ nativeSetError(mWebViewCore, code, message);
}
- /**
- * Get the global instance of MockGeolocation.
- * @return The global MockGeolocation instance.
- */
- public static MockGeolocation getInstance() {
- if (sMockGeolocation == null) {
- sMockGeolocation = new MockGeolocation();
- }
- return sMockGeolocation;
+ public void setPermission(boolean allow) {
+ // This should only ever be called on the WebKit thread.
+ nativeSetPermission(mWebViewCore, allow);
}
// Native functions
- private static native void nativeSetPosition(double latitude, double longitude, double accuracy);
- private static native void nativeSetError(int code, String message);
+ private static native void nativeSetUseMock(WebViewCore webViewCore);
+ private static native void nativeSetPosition(WebViewCore webViewCore, double latitude,
+ double longitude, double accuracy);
+ private static native void nativeSetError(WebViewCore webViewCore, int code, String message);
+ private static native void nativeSetPermission(WebViewCore webViewCore, boolean allow);
}
diff --git a/core/java/android/webkit/SearchBox.java b/core/java/android/webkit/SearchBox.java
deleted file mode 100644
index 38a1740..0000000
--- a/core/java/android/webkit/SearchBox.java
+++ /dev/null
@@ -1,109 +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.webkit;
-
-import java.util.List;
-
-/**
- * Defines the interaction between the browser/renderer and the page running on
- * a given WebView frame, if the page supports the chromium SearchBox API.
- *
- * http://dev.chromium.org/searchbox
- *
- * The browser or container app can query the page for search results using
- * SearchBox.query() and receive suggestions by registering a listener on the
- * SearchBox object.
- *
- * @hide
- */
-public interface SearchBox {
- /**
- * Sets the current searchbox query. Note that the caller must call
- * onchange() to ensure that the search page processes this query.
- */
- void setQuery(String query);
-
- /**
- * Verbatim is true if the caller suggests that the search page
- * treat the current query as a verbatim search query (as opposed to a
- * partially typed search query). As with setQuery, onchange() must be
- * called to ensure that the search page processes the query.
- */
- void setVerbatim(boolean verbatim);
-
- /**
- * These attributes must contain the offset to the characters that immediately
- * follow the start and end of the selection in the search box. If there is
- * no such selection, then both selectionStart and selectionEnd must be the offset
- * to the character that immediately follows the text entry cursor. In the case
- * that there is no explicit text entry cursor, the cursor is
- * implicitly at the end of the input.
- */
- void setSelection(int selectionStart, int selectionEnd);
-
- /**
- * Sets the dimensions of the view (if any) that overlaps the current
- * window object. This is to ensure that the page renders results in
- * a manner that allows them to not be obscured by such a view. Note
- * that a call to onresize() is required if these dimensions change.
- */
- void setDimensions(int x, int y, int width, int height);
-
- /**
- * Notify the search page of any changes to the searchbox. Such as
- * a change in the typed query (onchange), the user commiting a given query
- * (onsubmit), or a change in size of a suggestions dropdown (onresize).
- *
- * @param listener an optional listener to notify of the success of the operation,
- * indicating if the javascript function existed and could be called or not.
- * It will be called on the UI thread.
- */
- void onchange(SearchBoxListener listener);
- void onsubmit(SearchBoxListener listener);
- void onresize(SearchBoxListener listener);
- void oncancel(SearchBoxListener listener);
-
- /**
- * Add and remove listeners to the given Searchbox. Listeners are notified
- * of any suggestions to the query that the underlying search engine might
- * provide.
- */
- void addSearchBoxListener(SearchBoxListener l);
- void removeSearchBoxListener(SearchBoxListener l);
-
- /**
- * Indicates if the searchbox API is supported in the current page.
- */
- void isSupported(IsSupportedCallback callback);
-
- /**
- * Listeners (if any) will be called on the thread that created the
- * webview.
- */
- public abstract class SearchBoxListener {
- public void onSuggestionsReceived(String query, List<String> suggestions) {}
- public void onChangeComplete(boolean called) {}
- public void onSubmitComplete(boolean called) {}
- public void onResizeComplete(boolean called) {}
- public void onCancelComplete(boolean called) {}
- }
-
- interface IsSupportedCallback {
- void searchBoxIsSupported(boolean supported);
- }
-}
diff --git a/core/java/android/webkit/SearchBoxImpl.java b/core/java/android/webkit/SearchBoxImpl.java
deleted file mode 100644
index 9942d25..0000000
--- a/core/java/android/webkit/SearchBoxImpl.java
+++ /dev/null
@@ -1,304 +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.webkit;
-
-import android.text.TextUtils;
-import android.util.Log;
-import android.webkit.WebViewCore.EventHub;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONStringer;
-
-/**
- * The default implementation of the SearchBox interface. Implemented
- * as a java bridge object and a javascript adapter that is called into
- * by the page hosted in the frame.
- */
-final class SearchBoxImpl implements SearchBox {
- private static final String TAG = "WebKit.SearchBoxImpl";
-
- /* package */ static final String JS_INTERFACE_NAME = "searchBoxJavaBridge_";
-
- /* package */ static final String JS_BRIDGE
- = "(function()"
- + "{"
- + "if (!window.chrome) {"
- + " window.chrome = {};"
- + "}"
- + "if (!window.chrome.searchBox) {"
- + " var sb = window.chrome.searchBox = {};"
- + " sb.setSuggestions = function(suggestions) {"
- + " if (window.searchBoxJavaBridge_) {"
- + " window.searchBoxJavaBridge_.setSuggestions(JSON.stringify(suggestions));"
- + " }"
- + " };"
- + " sb.setValue = function(valueArray) { sb.value = valueArray[0]; };"
- + " sb.value = '';"
- + " sb.x = 0;"
- + " sb.y = 0;"
- + " sb.width = 0;"
- + " sb.height = 0;"
- + " sb.selectionStart = 0;"
- + " sb.selectionEnd = 0;"
- + " sb.verbatim = false;"
- + "}"
- + "})();";
-
- private static final String SET_QUERY_SCRIPT
- = "if (window.chrome && window.chrome.searchBox) {"
- + " window.chrome.searchBox.setValue(%s);"
- + "}";
-
- private static final String SET_VERBATIM_SCRIPT
- = "if (window.chrome && window.chrome.searchBox) {"
- + " window.chrome.searchBox.verbatim = %1$s;"
- + "}";
-
- private static final String SET_SELECTION_SCRIPT
- = "if (window.chrome && window.chrome.searchBox) {"
- + " var f = window.chrome.searchBox;"
- + " f.selectionStart = %d"
- + " f.selectionEnd = %d"
- + "}";
-
- private static final String SET_DIMENSIONS_SCRIPT
- = "if (window.chrome && window.chrome.searchBox) { "
- + " var f = window.chrome.searchBox;"
- + " f.x = %d;"
- + " f.y = %d;"
- + " f.width = %d;"
- + " f.height = %d;"
- + "}";
-
- private static final String DISPATCH_EVENT_SCRIPT
- = "if (window.chrome && window.chrome.searchBox && window.chrome.searchBox.on%1$s) {"
- + " window.chrome.searchBox.on%1$s();"
- + " window.searchBoxJavaBridge_.dispatchCompleteCallback('%1$s', %2$d, true);"
- + "} else {"
- + " window.searchBoxJavaBridge_.dispatchCompleteCallback('%1$s', %2$d, false);"
- + "}";
-
- private static final String EVENT_CHANGE = "change";
- private static final String EVENT_SUBMIT = "submit";
- private static final String EVENT_RESIZE = "resize";
- private static final String EVENT_CANCEL = "cancel";
-
- private static final String IS_SUPPORTED_SCRIPT
- = "if (window.searchBoxJavaBridge_) {"
- + " if (window.chrome && window.chrome.sv) {"
- + " window.searchBoxJavaBridge_.isSupportedCallback(true);"
- + " } else {"
- + " window.searchBoxJavaBridge_.isSupportedCallback(false);"
- + " }}";
-
- private final List<SearchBoxListener> mListeners;
- private final WebViewCore mWebViewCore;
- private final CallbackProxy mCallbackProxy;
- private IsSupportedCallback mSupportedCallback;
- private int mNextEventId = 1;
- private final HashMap<Integer, SearchBoxListener> mEventCallbacks;
-
- SearchBoxImpl(WebViewCore webViewCore, CallbackProxy callbackProxy) {
- mListeners = new ArrayList<SearchBoxListener>();
- mWebViewCore = webViewCore;
- mCallbackProxy = callbackProxy;
- mEventCallbacks = new HashMap<Integer, SearchBoxListener>();
- }
-
- @Override
- public void setQuery(String query) {
- final String formattedQuery = jsonSerialize(query);
- if (formattedQuery != null) {
- final String js = String.format(SET_QUERY_SCRIPT, formattedQuery);
- dispatchJs(js);
- }
- }
-
- @Override
- public void setVerbatim(boolean verbatim) {
- final String js = String.format(SET_VERBATIM_SCRIPT, String.valueOf(verbatim));
- dispatchJs(js);
- }
-
-
- @Override
- public void setSelection(int selectionStart, int selectionEnd) {
- final String js = String.format(SET_SELECTION_SCRIPT, selectionStart, selectionEnd);
- dispatchJs(js);
- }
-
- @Override
- public void setDimensions(int x, int y, int width, int height) {
- final String js = String.format(SET_DIMENSIONS_SCRIPT, x, y, width, height);
- dispatchJs(js);
- }
-
- @Override
- public void onchange(SearchBoxListener callback) {
- dispatchEvent(EVENT_CHANGE, callback);
- }
-
- @Override
- public void onsubmit(SearchBoxListener callback) {
- dispatchEvent(EVENT_SUBMIT, callback);
- }
-
- @Override
- public void onresize(SearchBoxListener callback) {
- dispatchEvent(EVENT_RESIZE, callback);
- }
-
- @Override
- public void oncancel(SearchBoxListener callback) {
- dispatchEvent(EVENT_CANCEL, callback);
- }
-
- private void dispatchEvent(String eventName, SearchBoxListener callback) {
- int eventId;
- if (callback != null) {
- synchronized(this) {
- eventId = mNextEventId++;
- mEventCallbacks.put(eventId, callback);
- }
- } else {
- eventId = 0;
- }
- final String js = String.format(DISPATCH_EVENT_SCRIPT, eventName, eventId);
- dispatchJs(js);
- }
-
- private void dispatchJs(String js) {
- mWebViewCore.sendMessage(EventHub.EXECUTE_JS, js);
- }
-
- @Override
- public void addSearchBoxListener(SearchBoxListener l) {
- synchronized (mListeners) {
- mListeners.add(l);
- }
- }
-
- @Override
- public void removeSearchBoxListener(SearchBoxListener l) {
- synchronized (mListeners) {
- mListeners.remove(l);
- }
- }
-
- @Override
- public void isSupported(IsSupportedCallback callback) {
- mSupportedCallback = callback;
- dispatchJs(IS_SUPPORTED_SCRIPT);
- }
-
- // Called by Javascript through the Java bridge.
- public void isSupportedCallback(boolean isSupported) {
- mCallbackProxy.onIsSupportedCallback(isSupported);
- }
-
- public void handleIsSupportedCallback(boolean isSupported) {
- IsSupportedCallback callback = mSupportedCallback;
- mSupportedCallback = null;
- if (callback != null) {
- callback.searchBoxIsSupported(isSupported);
- }
- }
-
- // Called by Javascript through the Java bridge.
- public void dispatchCompleteCallback(String function, int id, boolean successful) {
- mCallbackProxy.onSearchboxDispatchCompleteCallback(function, id, successful);
- }
-
- public void handleDispatchCompleteCallback(String function, int id, boolean successful) {
- if (id != 0) {
- SearchBoxListener listener;
- synchronized(this) {
- listener = mEventCallbacks.get(id);
- mEventCallbacks.remove(id);
- }
- if (listener != null) {
- if (TextUtils.equals(EVENT_CHANGE, function)) {
- listener.onChangeComplete(successful);
- } else if (TextUtils.equals(EVENT_SUBMIT, function)) {
- listener.onSubmitComplete(successful);
- } else if (TextUtils.equals(EVENT_RESIZE, function)) {
- listener.onResizeComplete(successful);
- } else if (TextUtils.equals(EVENT_CANCEL, function)) {
- listener.onCancelComplete(successful);
- }
- }
- }
- }
-
- // This is used as a hackish alternative to javascript escaping.
- // There appears to be no such functionality in the core framework.
- private static String jsonSerialize(String query) {
- JSONStringer stringer = new JSONStringer();
- try {
- stringer.array().value(query).endArray();
- } catch (JSONException e) {
- Log.w(TAG, "Error serializing query : " + query);
- return null;
- }
- return stringer.toString();
- }
-
- // Called by Javascript through the Java bridge.
- public void setSuggestions(String jsonArguments) {
- if (jsonArguments == null) {
- return;
- }
-
- String query = null;
- List<String> suggestions = new ArrayList<String>();
- try {
- JSONObject suggestionsJson = new JSONObject(jsonArguments);
- query = suggestionsJson.getString("query");
-
- final JSONArray suggestionsArray = suggestionsJson.getJSONArray("suggestions");
- for (int i = 0; i < suggestionsArray.length(); ++i) {
- final JSONObject suggestion = suggestionsArray.getJSONObject(i);
- final String value = suggestion.getString("value");
- if (value != null) {
- suggestions.add(value);
- }
- // We currently ignore the "type" of the suggestion. This isn't
- // documented anywhere in the API documents.
- // final String type = suggestions.getString("type");
- }
- } catch (JSONException je) {
- Log.w(TAG, "Error parsing json [" + jsonArguments + "], exception = " + je);
- return;
- }
-
- mCallbackProxy.onSearchboxSuggestionsReceived(query, suggestions);
- }
-
- /* package */ void handleSuggestions(String query, List<String> suggestions) {
- synchronized (mListeners) {
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- mListeners.get(i).onSuggestionsReceived(query, suggestions);
- }
- }
- }
-}
diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java
index 426145a..3a43950 100644
--- a/core/java/android/webkit/SslErrorHandler.java
+++ b/core/java/android/webkit/SslErrorHandler.java
@@ -26,9 +26,9 @@ import android.os.Handler;
public class SslErrorHandler extends Handler {
/**
- * Package-private constructor needed for API compatibility.
+ * @hide Only for use by WebViewProvider implementations.
*/
- SslErrorHandler() {}
+ public SslErrorHandler() {}
/**
* Proceed with the SSL certificate.
diff --git a/core/java/android/webkit/ViewStateSerializer.java b/core/java/android/webkit/ViewStateSerializer.java
index c161085..096d4cda 100644
--- a/core/java/android/webkit/ViewStateSerializer.java
+++ b/core/java/android/webkit/ViewStateSerializer.java
@@ -16,7 +16,6 @@
package android.webkit;
import android.graphics.Point;
-import android.graphics.Region;
import android.webkit.WebViewCore.DrawData;
import java.io.DataInputStream;
@@ -68,6 +67,15 @@ class ViewStateSerializer {
return draw;
}
+ public static void dumpLayerHierarchy(int baseLayer, OutputStream out, int level) {
+ nativeDumpLayerHierarchy(baseLayer, level, out,
+ new byte[WORKING_STREAM_STORAGE]);
+ }
+
+
+ private static native void nativeDumpLayerHierarchy(int baseLayer, int level,
+ OutputStream out, byte[] storage);
+
private static native boolean nativeSerializeViewState(int baseLayer,
OutputStream stream, byte[] storage);
diff --git a/core/java/android/webkit/WebBackForwardList.java b/core/java/android/webkit/WebBackForwardList.java
index 79e634e..bfef2e7 100644
--- a/core/java/android/webkit/WebBackForwardList.java
+++ b/core/java/android/webkit/WebBackForwardList.java
@@ -17,7 +17,6 @@
package android.webkit;
import java.io.Serializable;
-import java.util.ArrayList;
/**
* This class contains the back/forward list for a WebView.
@@ -25,22 +24,11 @@ import java.util.ArrayList;
* inspect the entries in the list.
*/
public class WebBackForwardList implements Cloneable, Serializable {
- // Current position in the list.
- private int mCurrentIndex;
- // ArrayList of WebHistoryItems for maintaining our copy.
- private ArrayList<WebHistoryItem> mArray;
- // Flag to indicate that the list is invalid
- private boolean mClearPending;
- // CallbackProxy to issue client callbacks.
- private final CallbackProxy mCallbackProxy;
/**
- * Construct a back/forward list used by clients of WebView.
+ * @hide
*/
- /*package*/ WebBackForwardList(CallbackProxy proxy) {
- mCurrentIndex = -1;
- mArray = new ArrayList<WebHistoryItem>();
- mCallbackProxy = proxy;
+ public WebBackForwardList() {
}
/**
@@ -49,7 +37,7 @@ public class WebBackForwardList implements Cloneable, Serializable {
* @return The current history item.
*/
public synchronized WebHistoryItem getCurrentItem() {
- return getItemAtIndex(mCurrentIndex);
+ throw new MustOverrideException();
}
/**
@@ -58,7 +46,7 @@ public class WebBackForwardList implements Cloneable, Serializable {
* @return The current index from 0...n or -1 if the list is empty.
*/
public synchronized int getCurrentIndex() {
- return mCurrentIndex;
+ throw new MustOverrideException();
}
/**
@@ -67,10 +55,7 @@ public class WebBackForwardList implements Cloneable, Serializable {
* @param index The index to retrieve.
*/
public synchronized WebHistoryItem getItemAtIndex(int index) {
- if (index < 0 || index >= getSize()) {
- return null;
- }
- return mArray.get(index);
+ throw new MustOverrideException();
}
/**
@@ -78,78 +63,7 @@ public class WebBackForwardList implements Cloneable, Serializable {
* @return The size of the list.
*/
public synchronized int getSize() {
- return mArray.size();
- }
-
- /**
- * Mark the back/forward list as having a pending clear. This is used on the
- * UI side to mark the list as being invalid during the clearHistory method.
- */
- /*package*/ synchronized void setClearPending() {
- mClearPending = true;
- }
-
- /**
- * Return the status of the clear flag. This is used on the UI side to
- * determine if the list is valid for checking things like canGoBack.
- */
- /*package*/ synchronized boolean getClearPending() {
- return mClearPending;
- }
-
- /**
- * Add a new history item to the list. This will remove all items after the
- * current item and append the new item to the end of the list. Called from
- * the WebCore thread only. Synchronized because the UI thread may be
- * reading the array or the current index.
- * @param item A new history item.
- */
- /*package*/ synchronized void addHistoryItem(WebHistoryItem item) {
- // Update the current position because we are going to add the new item
- // in that slot.
- ++mCurrentIndex;
- // If the current position is not at the end, remove all history items
- // after the current item.
- final int size = mArray.size();
- final int newPos = mCurrentIndex;
- if (newPos != size) {
- for (int i = size - 1; i >= newPos; i--) {
- final WebHistoryItem h = mArray.remove(i);
- }
- }
- // Add the item to the list.
- mArray.add(item);
- if (mCallbackProxy != null) {
- mCallbackProxy.onNewHistoryItem(item);
- }
- }
-
- /**
- * Clear the back/forward list. Called from the WebCore thread.
- */
- /*package*/ synchronized void close(int nativeFrame) {
- // Clear the array first because nativeClose will call addHistoryItem
- // with the current item.
- mArray.clear();
- mCurrentIndex = -1;
- nativeClose(nativeFrame);
- // Reset the clear flag
- mClearPending = false;
- }
-
- /* Remove the item at the given index. Called by JNI only. */
- private synchronized void removeHistoryItem(int index) {
- // XXX: This is a special case. Since the callback is only triggered
- // when removing the first item, we can assert that the index is 0.
- // This lets us change the current index without having to query the
- // native BackForwardList.
- if (DebugFlags.WEB_BACK_FORWARD_LIST && (index != 0)) {
- throw new AssertionError();
- }
- final WebHistoryItem h = mArray.remove(index);
- // XXX: If we ever add another callback for removing history items at
- // any index, this will no longer be valid.
- mCurrentIndex--;
+ throw new MustOverrideException();
}
/**
@@ -158,39 +72,7 @@ public class WebBackForwardList implements Cloneable, Serializable {
* webkit package classes.
*/
protected synchronized WebBackForwardList clone() {
- WebBackForwardList l = new WebBackForwardList(null);
- if (mClearPending) {
- // If a clear is pending, return a copy with only the current item.
- l.addHistoryItem(getCurrentItem());
- return l;
- }
- l.mCurrentIndex = mCurrentIndex;
- int size = getSize();
- l.mArray = new ArrayList<WebHistoryItem>(size);
- for (int i = 0; i < size; i++) {
- // Add a copy of each WebHistoryItem
- l.mArray.add(mArray.get(i).clone());
- }
- return l;
- }
-
- /**
- * Set the new history index.
- * @param newIndex The new history index.
- */
- /*package*/ synchronized void setCurrentIndex(int newIndex) {
- mCurrentIndex = newIndex;
- if (mCallbackProxy != null) {
- mCallbackProxy.onIndexChanged(getItemAtIndex(newIndex), newIndex);
- }
+ throw new MustOverrideException();
}
- /**
- * Restore the history index.
- */
- /*package*/ static native synchronized void restoreIndex(int nativeFrame,
- int index);
-
- /* Close the native list. */
- private static native void nativeClose(int nativeFrame);
}
diff --git a/core/java/android/webkit/WebBackForwardListClassic.java b/core/java/android/webkit/WebBackForwardListClassic.java
new file mode 100644
index 0000000..2a14e6b
--- /dev/null
+++ b/core/java/android/webkit/WebBackForwardListClassic.java
@@ -0,0 +1,166 @@
+/*
+ * 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 java.io.Serializable;
+import java.util.ArrayList;
+
+/* package */ class WebBackForwardListClassic extends WebBackForwardList implements Cloneable,
+ Serializable {
+
+ // Current position in the list.
+ private int mCurrentIndex;
+ // ArrayList of WebHistoryItems for maintaining our copy.
+ private ArrayList<WebHistoryItemClassic> mArray;
+ // Flag to indicate that the list is invalid
+ private boolean mClearPending;
+ // CallbackProxy to issue client callbacks.
+ private final CallbackProxy mCallbackProxy;
+
+ /*package*/ WebBackForwardListClassic(CallbackProxy proxy) {
+ mCurrentIndex = -1;
+ mArray = new ArrayList<WebHistoryItemClassic>();
+ mCallbackProxy = proxy;
+ }
+
+ public synchronized WebHistoryItemClassic getCurrentItem() {
+ return getItemAtIndex(mCurrentIndex);
+ }
+
+ public synchronized int getCurrentIndex() {
+ return mCurrentIndex;
+ }
+
+ public synchronized WebHistoryItemClassic getItemAtIndex(int index) {
+ if (index < 0 || index >= getSize()) {
+ return null;
+ }
+ return mArray.get(index);
+ }
+
+ public synchronized int getSize() {
+ return mArray.size();
+ }
+
+ /**
+ * Mark the back/forward list as having a pending clear. This is used on the
+ * UI side to mark the list as being invalid during the clearHistory method.
+ */
+ /*package*/ synchronized void setClearPending() {
+ mClearPending = true;
+ }
+
+ /**
+ * Return the status of the clear flag. This is used on the UI side to
+ * determine if the list is valid for checking things like canGoBack.
+ */
+ /*package*/ synchronized boolean getClearPending() {
+ return mClearPending;
+ }
+
+ /**
+ * Add a new history item to the list. This will remove all items after the
+ * current item and append the new item to the end of the list. Called from
+ * the WebCore thread only. Synchronized because the UI thread may be
+ * reading the array or the current index.
+ * @param item A new history item.
+ */
+ /*package*/ synchronized void addHistoryItem(WebHistoryItem item) {
+ // Update the current position because we are going to add the new item
+ // in that slot.
+ ++mCurrentIndex;
+ // If the current position is not at the end, remove all history items
+ // after the current item.
+ final int size = mArray.size();
+ final int newPos = mCurrentIndex;
+ if (newPos != size) {
+ for (int i = size - 1; i >= newPos; i--) {
+ final WebHistoryItem h = mArray.remove(i);
+ }
+ }
+ // Add the item to the list.
+ mArray.add((WebHistoryItemClassic) item);
+ if (mCallbackProxy != null) {
+ mCallbackProxy.onNewHistoryItem(item);
+ }
+ }
+
+ /**
+ * Clear the back/forward list. Called from the WebCore thread.
+ */
+ /*package*/ synchronized void close(int nativeFrame) {
+ // Clear the array first because nativeClose will call addHistoryItem
+ // with the current item.
+ mArray.clear();
+ mCurrentIndex = -1;
+ nativeClose(nativeFrame);
+ // Reset the clear flag
+ mClearPending = false;
+ }
+
+ /* Remove the item at the given index. Called by JNI only. */
+ private synchronized void removeHistoryItem(int index) {
+ // XXX: This is a special case. Since the callback is only triggered
+ // when removing the first item, we can assert that the index is 0.
+ // This lets us change the current index without having to query the
+ // native BackForwardList.
+ if (DebugFlags.WEB_BACK_FORWARD_LIST && (index != 0)) {
+ throw new AssertionError();
+ }
+ final WebHistoryItem h = mArray.remove(index);
+ // XXX: If we ever add another callback for removing history items at
+ // any index, this will no longer be valid.
+ mCurrentIndex--;
+ }
+
+ public synchronized WebBackForwardListClassic clone() {
+ WebBackForwardListClassic l = new WebBackForwardListClassic(null);
+ if (mClearPending) {
+ // If a clear is pending, return a copy with only the current item.
+ l.addHistoryItem(getCurrentItem());
+ return l;
+ }
+ l.mCurrentIndex = mCurrentIndex;
+ int size = getSize();
+ l.mArray = new ArrayList<WebHistoryItemClassic>(size);
+ for (int i = 0; i < size; i++) {
+ // Add a copy of each WebHistoryItem
+ l.mArray.add(mArray.get(i).clone());
+ }
+ return l;
+ }
+
+ /**
+ * Set the new history index.
+ * @param newIndex The new history index.
+ */
+ /*package*/ synchronized void setCurrentIndex(int newIndex) {
+ mCurrentIndex = newIndex;
+ if (mCallbackProxy != null) {
+ mCallbackProxy.onIndexChanged(getItemAtIndex(newIndex), newIndex);
+ }
+ }
+
+ /**
+ * Restore the history index.
+ */
+ /*package*/ static native synchronized void restoreIndex(int nativeFrame,
+ int index);
+
+ /* Close the native list. */
+ private static native void nativeClose(int nativeFrame);
+}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 4e8790b..e93db09 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -245,8 +245,8 @@ public class WebChromeClient {
}
/**
- * Tell the client that the quota has been reached for the Application Cache
- * API and request a new quota. The client must respond by invoking the
+ * Notify the host application that the Application Cache has reached the
+ * maximum size. The client must respond by invoking the
* {@link WebStorage.QuotaUpdater#updateQuota(long) updateQuota(long)}
* method of the supplied {@link WebStorage.QuotaUpdater} instance. The
* minimum value that can be set for the new quota is the current quota. The
@@ -255,7 +255,7 @@ public class WebChromeClient {
* @param requiredStorage The amount of storage required by the Application
* Cache operation that triggered this notification,
* in bytes.
- * @param quota The quota, in bytes
+ * @param quota the current maximum Application Cache size, in bytes
* @param quotaUpdater An instance of {@link WebStorage.QuotaUpdater} which
* must be used to inform the WebView of the new quota.
*/
@@ -297,7 +297,12 @@ public class WebChromeClient {
* will continue to occur if the script does not finish at the next check
* point.
* @return boolean Whether the JavaScript execution should be interrupted.
+ * @deprecated This method is no longer supported and will not be invoked.
*/
+ // This method was only called when using the JSC javascript engine. V8 became
+ // the default JS engine with Froyo and support for building with JSC was
+ // removed in b/5495373. V8 does not have a mechanism for making a callback such
+ // as this.
public boolean onJsTimeout() {
return true;
}
@@ -372,13 +377,6 @@ public class WebChromeClient {
}
/**
- * Tell the client that the page being viewed is web app capable,
- * i.e. has specified the fullscreen-web-app-capable meta tag.
- * @hide
- */
- public void setInstallableWebApp() { }
-
- /**
* Tell the client that the page being viewed has an autofillable
* form and the user would like to set a profile up.
* @param msg A Message to send once the user has successfully
diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java
index 788d05c..9a588e4 100644
--- a/core/java/android/webkit/WebHistoryItem.java
+++ b/core/java/android/webkit/WebHistoryItem.java
@@ -18,9 +18,6 @@ package android.webkit;
import android.graphics.Bitmap;
-import java.net.MalformedURLException;
-import java.net.URL;
-
/**
* A convenience class for accessing fields in an entry in the back/forward list
* of a WebView. Each WebHistoryItem is a snapshot of the requested history
@@ -28,67 +25,11 @@ import java.net.URL;
* @see WebBackForwardList
*/
public class WebHistoryItem implements Cloneable {
- // Global identifier count.
- private static int sNextId = 0;
- // Unique identifier.
- private final int mId;
- // A point to a native WebHistoryItem instance which contains the actual data
- private int mNativeBridge;
- // The favicon for this item.
- private Bitmap mFavicon;
- // The pre-flattened data used for saving the state.
- private byte[] mFlattenedData;
- // The apple-touch-icon url for use when adding the site to the home screen,
- // as obtained from a <link> element in the page.
- private String mTouchIconUrlFromLink;
- // If no <link> is specified, this holds the default location of the
- // apple-touch-icon.
- private String mTouchIconUrlServerDefault;
- // Custom client data that is not flattened or read by native code.
- private Object mCustomData;
-
- /**
- * Basic constructor that assigns a unique id to the item. Called by JNI
- * only.
- */
- private WebHistoryItem(int nativeBridge) {
- synchronized (WebHistoryItem.class) {
- mId = sNextId++;
- }
- mNativeBridge = nativeBridge;
- nativeRef(mNativeBridge);
- }
-
- protected void finalize() throws Throwable {
- if (mNativeBridge != 0) {
- nativeUnref(mNativeBridge);
- mNativeBridge = 0;
- }
- }
/**
- * Construct a new WebHistoryItem with initial flattened data.
- * @param data The pre-flattened data coming from restoreState.
- */
- /*package*/ WebHistoryItem(byte[] data) {
- mFlattenedData = data;
- synchronized (WebHistoryItem.class) {
- mId = sNextId++;
- }
- }
-
- /**
- * Construct a clone of a WebHistoryItem from the given item.
- * @param item The history item to clone.
+ * @hide
*/
- private WebHistoryItem(WebHistoryItem item) {
- mFlattenedData = item.mFlattenedData;
- mId = item.mId;
- mFavicon = item.mFavicon;
- mNativeBridge = item.mNativeBridge;
- if (mNativeBridge != 0) {
- nativeRef(mNativeBridge);
- }
+ public WebHistoryItem() {
}
/**
@@ -97,10 +38,11 @@ public class WebHistoryItem implements Cloneable {
* same object.
* @return The id for this item.
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public int getId() {
- return mId;
+ throw new MustOverrideException();
}
/**
@@ -112,8 +54,7 @@ public class WebHistoryItem implements Cloneable {
* to synchronize this method.
*/
public String getUrl() {
- if (mNativeBridge == 0) return null;
- return nativeGetUrl(mNativeBridge);
+ throw new MustOverrideException();
}
/**
@@ -123,8 +64,7 @@ public class WebHistoryItem implements Cloneable {
* @return The original url of this history item.
*/
public String getOriginalUrl() {
- if (mNativeBridge == 0) return null;
- return nativeGetOriginalUrl(mNativeBridge);
+ throw new MustOverrideException();
}
/**
@@ -134,8 +74,7 @@ public class WebHistoryItem implements Cloneable {
* to synchronize this method.
*/
public String getTitle() {
- if (mNativeBridge == 0) return null;
- return nativeGetTitle(mNativeBridge);
+ throw new MustOverrideException();
}
/**
@@ -145,119 +84,14 @@ public class WebHistoryItem implements Cloneable {
* to synchronize this method.
*/
public Bitmap getFavicon() {
- if (mFavicon == null && mNativeBridge != 0) {
- mFavicon = nativeGetFavicon(mNativeBridge);
- }
- return mFavicon;
- }
-
- /**
- * Return the touch icon url.
- * If no touch icon <link> tag was specified, returns
- * <host>/apple-touch-icon.png. The DownloadTouchIcon class that
- * attempts to retrieve the touch icon will handle the case where
- * that file does not exist. An icon set by a <link> tag is always
- * used in preference to an icon saved on the server.
- * @hide
- */
- public String getTouchIconUrl() {
- if (mTouchIconUrlFromLink != null) {
- return mTouchIconUrlFromLink;
- } else if (mTouchIconUrlServerDefault != null) {
- return mTouchIconUrlServerDefault;
- }
-
- try {
- URL url = new URL(getOriginalUrl());
- mTouchIconUrlServerDefault = new URL(url.getProtocol(), url.getHost(), url.getPort(),
- "/apple-touch-icon.png").toString();
- } catch (MalformedURLException e) {
- return null;
- }
- return mTouchIconUrlServerDefault;
- }
-
- /**
- * Return the custom data provided by the client.
- * @hide
- */
- public Object getCustomData() {
- return mCustomData;
- }
-
- /**
- * Set the custom data field.
- * @param data An Object containing any data the client wishes to associate
- * with the item.
- * @hide
- */
- public void setCustomData(Object data) {
- // NOTE: WebHistoryItems are used in multiple threads. However, the
- // public facing apis are all getters with the exception of this one
- // api. Since this api is exclusive to clients, we don't make any
- // promises about thread safety.
- mCustomData = data;
- }
-
- /**
- * Set the favicon.
- * @param icon A Bitmap containing the favicon for this history item.
- * Note: The VM ensures 32-bit atomic read/write operations so we don't have
- * to synchronize this method.
- */
- /*package*/ void setFavicon(Bitmap icon) {
- mFavicon = icon;
- }
-
- /**
- * Set the touch icon url. Will not overwrite an icon that has been
- * set already from a <link> tag, unless the new icon is precomposed.
- * @hide
- */
- /*package*/ void setTouchIconUrl(String url, boolean precomposed) {
- if (precomposed || mTouchIconUrlFromLink == null) {
- mTouchIconUrlFromLink = url;
- }
- }
-
- /**
- * Get the pre-flattened data.
- * Note: The VM ensures 32-bit atomic read/write operations so we don't have
- * to synchronize this method.
- */
- /*package*/ byte[] getFlattenedData() {
- if (mNativeBridge != 0) {
- return nativeGetFlattenedData(mNativeBridge);
- }
- return mFlattenedData;
- }
-
- /**
- * Inflate this item.
- * Note: The VM ensures 32-bit atomic read/write operations so we don't have
- * to synchronize this method.
- */
- /*package*/ void inflate(int nativeFrame) {
- mNativeBridge = inflate(nativeFrame, mFlattenedData);
- mFlattenedData = null;
+ throw new MustOverrideException();
}
/**
* Clone the history item for use by clients of WebView.
*/
protected synchronized WebHistoryItem clone() {
- return new WebHistoryItem(this);
+ throw new MustOverrideException();
}
- /* Natively inflate this item, this method is called in the WebCore thread.
- */
- private native int inflate(int nativeFrame, byte[] data);
- private native void nativeRef(int nptr);
- private native void nativeUnref(int nptr);
- private native String nativeGetTitle(int nptr);
- private native String nativeGetUrl(int nptr);
- private native String nativeGetOriginalUrl(int nptr);
- private native byte[] nativeGetFlattenedData(int nptr);
- private native Bitmap nativeGetFavicon(int nptr);
-
}
diff --git a/core/java/android/webkit/WebHistoryItemClassic.java b/core/java/android/webkit/WebHistoryItemClassic.java
new file mode 100644
index 0000000..1620fbf
--- /dev/null
+++ b/core/java/android/webkit/WebHistoryItemClassic.java
@@ -0,0 +1,221 @@
+/*
+ * 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.graphics.Bitmap;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/* package */ class WebHistoryItemClassic extends WebHistoryItem implements Cloneable {
+ // Global identifier count.
+ private static int sNextId = 0;
+ // Unique identifier.
+ private final int mId;
+ // A point to a native WebHistoryItem instance which contains the actual data
+ private int mNativeBridge;
+ // The favicon for this item.
+ private Bitmap mFavicon;
+ // The pre-flattened data used for saving the state.
+ private byte[] mFlattenedData;
+ // The apple-touch-icon url for use when adding the site to the home screen,
+ // as obtained from a <link> element in the page.
+ private String mTouchIconUrlFromLink;
+ // If no <link> is specified, this holds the default location of the
+ // apple-touch-icon.
+ private String mTouchIconUrlServerDefault;
+ // Custom client data that is not flattened or read by native code.
+ private Object mCustomData;
+
+ /**
+ * Basic constructor that assigns a unique id to the item. Called by JNI
+ * only.
+ */
+ private WebHistoryItemClassic(int nativeBridge) {
+ synchronized (WebHistoryItemClassic.class) {
+ mId = sNextId++;
+ }
+ mNativeBridge = nativeBridge;
+ nativeRef(mNativeBridge);
+ }
+
+ protected void finalize() throws Throwable {
+ if (mNativeBridge != 0) {
+ nativeUnref(mNativeBridge);
+ mNativeBridge = 0;
+ }
+ }
+
+ /**
+ * Construct a new WebHistoryItem with initial flattened data.
+ * @param data The pre-flattened data coming from restoreState.
+ */
+ /*package*/ WebHistoryItemClassic(byte[] data) {
+ mFlattenedData = data;
+ synchronized (WebHistoryItemClassic.class) {
+ mId = sNextId++;
+ }
+ }
+
+ /**
+ * Construct a clone of a WebHistoryItem from the given item.
+ * @param item The history item to clone.
+ */
+ private WebHistoryItemClassic(WebHistoryItemClassic item) {
+ mFlattenedData = item.mFlattenedData;
+ mId = item.mId;
+ mFavicon = item.mFavicon;
+ mNativeBridge = item.mNativeBridge;
+ if (mNativeBridge != 0) {
+ nativeRef(mNativeBridge);
+ }
+ }
+
+ @Deprecated
+ public int getId() {
+ return mId;
+ }
+
+ public String getUrl() {
+ if (mNativeBridge == 0) return null;
+ return nativeGetUrl(mNativeBridge);
+ }
+
+ public String getOriginalUrl() {
+ if (mNativeBridge == 0) return null;
+ return nativeGetOriginalUrl(mNativeBridge);
+ }
+
+ public String getTitle() {
+ if (mNativeBridge == 0) return null;
+ return nativeGetTitle(mNativeBridge);
+ }
+
+ public Bitmap getFavicon() {
+ if (mFavicon == null && mNativeBridge != 0) {
+ mFavicon = nativeGetFavicon(mNativeBridge);
+ }
+ return mFavicon;
+ }
+
+ /**
+ * Return the touch icon url.
+ * If no touch icon <link> tag was specified, returns
+ * <host>/apple-touch-icon.png. The DownloadTouchIcon class that
+ * attempts to retrieve the touch icon will handle the case where
+ * that file does not exist. An icon set by a <link> tag is always
+ * used in preference to an icon saved on the server.
+ * @hide
+ */
+ public String getTouchIconUrl() {
+ if (mTouchIconUrlFromLink != null) {
+ return mTouchIconUrlFromLink;
+ } else if (mTouchIconUrlServerDefault != null) {
+ return mTouchIconUrlServerDefault;
+ }
+
+ try {
+ URL url = new URL(getOriginalUrl());
+ mTouchIconUrlServerDefault = new URL(url.getProtocol(), url.getHost(), url.getPort(),
+ "/apple-touch-icon.png").toString();
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ return mTouchIconUrlServerDefault;
+ }
+
+ /**
+ * Return the custom data provided by the client.
+ * @hide
+ */
+ public Object getCustomData() {
+ return mCustomData;
+ }
+
+ /**
+ * Set the custom data field.
+ * @param data An Object containing any data the client wishes to associate
+ * with the item.
+ * @hide
+ */
+ public void setCustomData(Object data) {
+ // NOTE: WebHistoryItems are used in multiple threads. However, the
+ // public facing apis are all getters with the exception of this one
+ // api. Since this api is exclusive to clients, we don't make any
+ // promises about thread safety.
+ mCustomData = data;
+ }
+
+ /**
+ * Set the favicon.
+ * @param icon A Bitmap containing the favicon for this history item.
+ * Note: The VM ensures 32-bit atomic read/write operations so we don't have
+ * to synchronize this method.
+ */
+ /*package*/ void setFavicon(Bitmap icon) {
+ mFavicon = icon;
+ }
+
+ /**
+ * Set the touch icon url. Will not overwrite an icon that has been
+ * set already from a <link> tag, unless the new icon is precomposed.
+ * @hide
+ */
+ /*package*/ void setTouchIconUrl(String url, boolean precomposed) {
+ if (precomposed || mTouchIconUrlFromLink == null) {
+ mTouchIconUrlFromLink = url;
+ }
+ }
+
+ /**
+ * Get the pre-flattened data.
+ * Note: The VM ensures 32-bit atomic read/write operations so we don't have
+ * to synchronize this method.
+ */
+ /*package*/ byte[] getFlattenedData() {
+ if (mNativeBridge != 0) {
+ return nativeGetFlattenedData(mNativeBridge);
+ }
+ return mFlattenedData;
+ }
+
+ /**
+ * Inflate this item.
+ * Note: The VM ensures 32-bit atomic read/write operations so we don't have
+ * to synchronize this method.
+ */
+ /*package*/ void inflate(int nativeFrame) {
+ mNativeBridge = inflate(nativeFrame, mFlattenedData);
+ mFlattenedData = null;
+ }
+
+ public synchronized WebHistoryItemClassic clone() {
+ return new WebHistoryItemClassic(this);
+ }
+
+ /* Natively inflate this item, this method is called in the WebCore thread.
+ */
+ private native int inflate(int nativeFrame, byte[] data);
+ private native void nativeRef(int nptr);
+ private native void nativeUnref(int nptr);
+ private native String nativeGetTitle(int nptr);
+ private native String nativeGetUrl(int nptr);
+ private native String nativeGetOriginalUrl(int nptr);
+ private native byte[] nativeGetFlattenedData(int nptr);
+ private native Bitmap nativeGetFavicon(int nptr);
+
+}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index fa3cb20..aa68904 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -16,8 +16,7 @@
package android.webkit;
-import android.os.Message;
-import android.os.Build;
+import android.content.Context;
/**
* Manages settings state for a WebView. When a WebView is first created, it
@@ -94,30 +93,38 @@ public abstract class WebSettings {
}
/**
- * Default cache usage pattern. Use with {@link #setCacheMode}.
+ * Default cache usage mode. If the navigation type doesn't impose any
+ * specific behavior, use cached resources when they are available
+ * and not expired, otherwise load resources from the network.
+ * Use with {@link #setCacheMode}.
*/
public static final int LOAD_DEFAULT = -1;
/**
- * Normal cache usage pattern. Use with {@link #setCacheMode}.
+ * Normal cache usage mode. Use with {@link #setCacheMode}.
+ *
+ * @deprecated This value is obsolete, as from API level
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and onwards it has the
+ * same effect as {@link #LOAD_DEFAULT}.
*/
+ @Deprecated
public static final int LOAD_NORMAL = 0;
/**
- * Use cache if content is there, even if expired (eg, history nav).
- * If it is not in the cache, load from network.
+ * Use cached resources when they are available, even if they have expired.
+ * Otherwise load resources from the network.
* Use with {@link #setCacheMode}.
*/
public static final int LOAD_CACHE_ELSE_NETWORK = 1;
/**
- * Don't use the cache, load from network.
+ * Don't use the cache, load from the network.
* Use with {@link #setCacheMode}.
*/
public static final int LOAD_NO_CACHE = 2;
/**
- * Don't use the network, load from cache only.
+ * Don't use the network, load from the cache.
* Use with {@link #setCacheMode}.
*/
public static final int LOAD_CACHE_ONLY = 3;
@@ -153,9 +160,11 @@ public abstract class WebSettings {
}
/**
- * Enables dumping the pages navigation cache to a text file.
+ * Enables dumping the pages navigation cache to a text file. The default
+ * is false.
*
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public void setNavDump(boolean enabled) {
@@ -165,7 +174,10 @@ public abstract class WebSettings {
/**
* Gets whether dumping the navigation cache is enabled.
*
+ * @return whether dumping the navigation cache is enabled
+ * @see #setNavDump
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public boolean getNavDump() {
@@ -196,6 +208,26 @@ public abstract class WebSettings {
}
/**
+ * Sets whether the WebView requires a user gesture to play media.
+ * The default is true.
+ *
+ * @param require whether the WebView requires a user gesture to play media
+ */
+ public void setMediaPlaybackRequiresUserGesture(boolean require) {
+ throw new MustOverrideException();
+ }
+
+ /**
+ * Gets whether the WebView requires a user gesture to play media.
+ *
+ * @return true if the WebView requires a user gesture to play media
+ * @see #setMediaPlaybackRequiresUserGesture
+ */
+ public boolean getMediaPlaybackRequiresUserGesture() {
+ throw new MustOverrideException();
+ }
+
+ /**
* Sets whether the WebView should use its built-in zoom mechanisms. The
* built-in zoom mechanisms comprise on-screen zoom controls, which are
* displayed over the WebView's content, and the use of a pinch gesture to
@@ -285,14 +317,18 @@ public abstract class WebSettings {
}
/**
- * Sets whether the WebView loads a page with overview mode.
+ * Sets whether the WebView loads pages in overview mode. The default is
+ * false.
*/
public void setLoadWithOverviewMode(boolean overview) {
throw new MustOverrideException();
}
/**
- * Gets whether this WebView loads pages with overview mode.
+ * Gets whether this WebView loads pages in overview mode.
+ *
+ * @return whether this WebView loads pages in overview mode
+ * @see #setLoadWithOverviewMode
*/
public boolean getLoadWithOverviewMode() {
throw new MustOverrideException();
@@ -304,7 +340,10 @@ public abstract class WebSettings {
* If it is true, WebView will choose a solution to maximize the performance.
* e.g. the WebView's content may not be updated during the transition.
* If it is false, WebView will keep its fidelity. The default value is false.
+ *
+ * @deprecated This method is now obsolete, and will become a no-op in future.
*/
+ @Deprecated
public void setEnableSmoothTransition(boolean enable) {
throw new MustOverrideException();
}
@@ -314,7 +353,10 @@ public abstract class WebSettings {
* zooming.
*
* @see #setEnableSmoothTransition
+ *
+ * @deprecated This method is now obsolete, and will become a no-op in future.
*/
+ @Deprecated
public boolean enableSmoothTransition() {
throw new MustOverrideException();
}
@@ -325,6 +367,7 @@ public abstract class WebSettings {
* internal pattern. Default is true.
*
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public void setUseWebViewBackgroundForOverscrollBackground(boolean view) {
@@ -337,6 +380,7 @@ public abstract class WebSettings {
*
* @see #setUseWebViewBackgroundForOverscrollBackground
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public boolean getUseWebViewBackgroundForOverscrollBackground() {
@@ -344,38 +388,45 @@ public abstract class WebSettings {
}
/**
- * Sets whether the WebView is saving form data.
+ * Sets whether the WebView should save form data. The default is true,
+ * unless in private browsing mode, when the value is always false.
*/
public void setSaveFormData(boolean save) {
throw new MustOverrideException();
}
/**
- * Gets whether the WebView is saving form data and displaying prior
- * entries/autofill++. Always false in private browsing mode.
+ * Gets whether the WebView saves form data. Always false in private
+ * browsing mode.
+ *
+ * @return whether the WebView saves form data
+ * @see #setSaveFormData
*/
public boolean getSaveFormData() {
throw new MustOverrideException();
}
/**
- * Stores whether the WebView is saving password.
+ * Sets whether the WebView should save passwords. The default is true.
*/
public void setSavePassword(boolean save) {
throw new MustOverrideException();
}
/**
- * Gets whether the WebView is saving password.
+ * Gets whether the WebView saves passwords.
+ *
+ * @return whether the WebView saves passwords
+ * @see #setSavePassword
*/
public boolean getSavePassword() {
throw new MustOverrideException();
}
/**
- * Sets the text zoom of the page in percent. Default is 100.
+ * Sets the text zoom of the page in percent. The default is 100.
*
- * @param textZoom the percent value for increasing or decreasing the text
+ * @param textZoom the text zoom in percent
*/
public synchronized void setTextZoom(int textZoom) {
throw new MustOverrideException();
@@ -384,53 +435,65 @@ public abstract class WebSettings {
/**
* Gets the text zoom of the page in percent.
*
- * @return a percent value describing the text zoom
- * @see #setTextSizeZoom
+ * @return the text zoom of the page in percent
+ * @see #setTextZoom
*/
public synchronized int getTextZoom() {
throw new MustOverrideException();
}
/**
- * Sets the text size of the page.
+ * Sets the text size of the page. The default is {@link TextSize#NORMAL}.
*
- * @param t the TextSize value for increasing or decreasing the text
- * @see WebSettings.TextSize
- * @deprecated Use {@link #setTextZoom(int)} instead.
+ * @param t the text size as a {@link TextSize} value
+ * @deprecated Use {@link #setTextZoom} instead.
*/
public synchronized void setTextSize(TextSize t) {
- throw new MustOverrideException();
+ setTextZoom(t.value);
}
/**
* Gets the text size of the page. If the text size was previously specified
- * in percent using {@link #setTextZoom(int)}, this will return
- * the closest matching {@link TextSize}.
+ * in percent using {@link #setTextZoom}, this will return the closest
+ * matching {@link TextSize}.
*
- * @return a TextSize enum value describing the text size
- * @see WebSettings.TextSize
- * @deprecated Use {@link #getTextZoom()} instead.
+ * @return the text size as a {@link TextSize} value
+ * @see #setTextSize
+ * @deprecated Use {@link #getTextZoom} instead.
*/
public synchronized TextSize getTextSize() {
- throw new MustOverrideException();
+ TextSize closestSize = null;
+ int smallestDelta = Integer.MAX_VALUE;
+ int textSize = getTextZoom();
+ for (TextSize size : TextSize.values()) {
+ int delta = Math.abs(textSize - size.value);
+ if (delta == 0) {
+ return size;
+ }
+ if (delta < smallestDelta) {
+ smallestDelta = delta;
+ closestSize = size;
+ }
+ }
+ return closestSize != null ? closestSize : TextSize.NORMAL;
}
/**
- * Sets the default zoom density of the page. This should be called from UI
- * thread.
+ * Sets the default zoom density of the page. This must be called from the UI
+ * thread. The default is {@link ZoomDensity#MEDIUM}.
*
- * @param zoom a ZoomDensity value
- * @see WebSettings.ZoomDensity
+ * @param zoom the zoom density
*/
public void setDefaultZoom(ZoomDensity zoom) {
throw new MustOverrideException();
}
/**
- * Gets the default zoom density of the page. This should be called from UI
- * thread.
- * @return a ZoomDensity value
- * @see WebSettings.ZoomDensity
+ * Gets the default zoom density of the page. This should be called from
+ * the UI thread.
+ *
+ * @return the zoom density
+ * @see #setDefaultZoom
*/
public ZoomDensity getDefaultZoom() {
throw new MustOverrideException();
@@ -438,6 +501,7 @@ public abstract class WebSettings {
/**
* Enables using light touches to make a selection and activate mouseovers.
+ * The default is false.
*/
public void setLightTouchEnabled(boolean enabled) {
throw new MustOverrideException();
@@ -445,6 +509,9 @@ public abstract class WebSettings {
/**
* Gets whether light touches are enabled.
+ *
+ * @return whether light touches are enabled
+ * @see #setLightTouchEnabled
*/
public boolean getLightTouchEnabled() {
throw new MustOverrideException();
@@ -455,6 +522,7 @@ public abstract class WebSettings {
* it now has no effect.
*
* @deprecated This setting now has no effect.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public synchronized void setUseDoubleTree(boolean use) {
@@ -466,6 +534,7 @@ public abstract class WebSettings {
* it now has no effect.
*
* @deprecated This setting now has no effect.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public synchronized boolean getUseDoubleTree() {
@@ -474,11 +543,17 @@ public abstract class WebSettings {
}
/**
- * Tells the WebView about user-agent string.
+ * Sets the user-agent string using an integer code.
+ * <ul>
+ * <li>0 means the WebView should use an Android user-agent string</li>
+ * <li>1 means the WebView should use a desktop user-agent string</li>
+ * </ul>
+ * Other values are ignored. The default is an Android user-agent string,
+ * i.e. code value 0.
*
- * @param ua 0 if the WebView should use an Android user-agent string,
- * 1 if the WebView should use a desktop user-agent string
- * @deprecated Please use setUserAgentString instead.
+ * @param ua the integer code for the user-agent string
+ * @deprecated Please use {@link #setUserAgentString} instead.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public synchronized void setUserAgent(int ua) {
@@ -486,12 +561,18 @@ public abstract class WebSettings {
}
/**
- * Gets the user-agent as an int.
+ * Gets the user-agent as an integer code.
+ * <ul>
+ * <li>-1 means the WebView is using a custom user-agent string set with
+ * {@link #setUserAgentString}</li>
+ * <li>0 means the WebView should use an Android user-agent string</li>
+ * <li>1 means the WebView should use a desktop user-agent string</li>
+ * </ul>
*
- * @return 0 if the WebView is using an Android user-agent string,
- * 1 if the WebView is using a desktop user-agent string,
- * -1 if the WebView is using user defined user-agent string
- * @deprecated Please use getUserAgentString instead.
+ * @return the integer code for the user-agent string
+ * @see #setUserAgent
+ * @deprecated Please use {@link #getUserAgentString} instead.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public synchronized int getUserAgent() {
@@ -499,7 +580,9 @@ public abstract class WebSettings {
}
/**
- * Tells the WebView to use the wide viewport.
+ * Tells the WebView to use a wide viewport. The default is false.
+ *
+ * @param use whether to use a wide viewport
*/
public synchronized void setUseWideViewPort(boolean use) {
throw new MustOverrideException();
@@ -509,26 +592,28 @@ public abstract class WebSettings {
* Gets whether the WebView is using a wide viewport.
*
* @return true if the WebView is using a wide viewport
+ * @see #setUseWideViewPort
*/
public synchronized boolean getUseWideViewPort() {
throw new MustOverrideException();
}
/**
- * Tells the WebView whether it supports multiple windows. TRUE means
- * that {@link WebChromeClient#onCreateWindow(WebView, boolean,
- * boolean, Message)} is implemented by the host application.
+ * Sets whether the WebView whether supports multiple windows. If set to
+ * true, {@link WebChromeClient#onCreateWindow} must be implemented by the
+ * host application. The default is false.
+ *
+ * @param support whether to suport multiple windows
*/
public synchronized void setSupportMultipleWindows(boolean support) {
throw new MustOverrideException();
}
/**
- * Gets whether the WebView is supporting multiple windows.
+ * Gets whether the WebView supports multiple windows.
*
- * @return true if the WebView is supporting multiple windows. This means
- * that {@link WebChromeClient#onCreateWindow(WebView, boolean,
- * boolean, Message)} is implemented by the host application.
+ * @return true if the WebView supports multiple windows
+ * @see #setSupportMultipleWindows
*/
public synchronized boolean supportMultipleWindows() {
throw new MustOverrideException();
@@ -536,10 +621,9 @@ public abstract class WebSettings {
/**
* Sets the underlying layout algorithm. This will cause a relayout of the
- * WebView. The default is NARROW_COLUMNS.
+ * WebView. The default is {@link LayoutAlgorithm#NARROW_COLUMNS}.
*
- * @param l a LayoutAlgorithm enum specifying the algorithm to use
- * @see WebSettings.LayoutAlgorithm
+ * @param l the layout algorithm to use, as a {@link LayoutAlgorithm} value
*/
public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) {
throw new MustOverrideException();
@@ -548,10 +632,8 @@ public abstract class WebSettings {
/**
* Gets the current layout algorithm.
*
- * @return a LayoutAlgorithm enum value describing the layout algorithm
- * being used
+ * @return the layout algorithm in use, as a {@link LayoutAlgorithm} value
* @see #setLayoutAlgorithm
- * @see WebSettings.LayoutAlgorithm
*/
public synchronized LayoutAlgorithm getLayoutAlgorithm() {
throw new MustOverrideException();
@@ -596,7 +678,7 @@ public abstract class WebSettings {
}
/**
- * Sets the sans-serif font family name.
+ * Sets the sans-serif font family name. The default is "sans-serif".
*
* @param font a font family name
*/
@@ -608,6 +690,7 @@ public abstract class WebSettings {
* Gets the sans-serif font family name.
*
* @return the sans-serif font family name as a string
+ * @see #setSansSerifFontFamily
*/
public synchronized String getSansSerifFontFamily() {
throw new MustOverrideException();
@@ -883,9 +966,9 @@ public abstract class WebSettings {
public abstract void setAllowFileAccessFromFileURLs(boolean flag);
/**
- * Tells the WebView to enable plugins.
+ * Sets whether the WebView should enable plugins. The default is false.
*
- * @param flag true if the WebView should load plugins
+ * @param flag true if plugins should be enabled
* @deprecated This method has been deprecated in favor of
* {@link #setPluginState}
*/
@@ -898,7 +981,8 @@ public abstract class WebSettings {
* Tells the WebView to enable, disable, or have plugins on demand. On
* demand mode means that if a plugin exists that can handle the embedded
* content, a placeholder icon will be shown instead of the plugin. When
- * the placeholder is clicked, the plugin will be enabled.
+ * the placeholder is clicked, the plugin will be enabled. The default is
+ * {@link PluginState#OFF}.
*
* @param state a PluginState value
*/
@@ -921,23 +1005,27 @@ public abstract class WebSettings {
/**
* Sets the path to where database storage API databases should be saved.
- * Note that the WebCore Database Tracker only allows the path to be set once.
+ * In order for the database storage API to function correctly, this method
+ * must be called with a path to which the application can write. This
+ * method should only be called once: repeated calls are ignored.
*
- * @param databasePath a String path to the directory where databases should
- * be saved. May be the empty string but should never
- * be null.
+ * @param databasePath a path to the directory where databases should be
+ * saved.
*/
// This will update WebCore when the Sync runs in the C++ side.
+ // Note that the WebCore Database Tracker only allows the path to be set
+ // once.
public synchronized void setDatabasePath(String databasePath) {
throw new MustOverrideException();
}
/**
- * Sets the path where the Geolocation permissions database should be saved.
+ * Sets the path where the Geolocation databases should be saved. In order
+ * for Geolocation permissions and cached positions to be persisted, this
+ * method must be called with a path to which the application can write.
*
- * @param databasePath a String path to the directory where the Geolocation
- * permissions database should be saved. May be the
- * empty string but should never be null.
+ * @param databasePath a path to the directory where databases should be
+ * saved.
*/
// This will update WebCore when the Sync runs in the C++ side.
public synchronized void setGeolocationDatabasePath(String databasePath) {
@@ -945,7 +1033,10 @@ public abstract class WebSettings {
}
/**
- * Tells the WebView to enable Application Caches API.
+ * Sets whether the Application Caches API should be enabled. The default
+ * is false. Note that in order for the Application Caches API to be
+ * enabled, a valid database path must also be supplied to
+ * {@link #setAppCachePath}.
*
* @param flag true if the WebView should enable Application Caches
*/
@@ -954,20 +1045,25 @@ public abstract class WebSettings {
}
/**
- * Sets a custom path to the Application Caches files. The client
- * must ensure it exists before this call.
+ * Sets the path to the Application Caches files. In order for the
+ * Application Caches API to be enabled, this method must be called with a
+ * path to which the application can write. This method should only be
+ * called once: repeated calls are ignored.
*
* @param appCachePath a String path to the directory containing
- * Application Caches files. The appCache path can be
- * the empty string but should not be null. Passing
- * null for this parameter will result in a no-op.
+ * Application Caches files.
+ * @see setAppCacheEnabled
*/
public synchronized void setAppCachePath(String appCachePath) {
throw new MustOverrideException();
}
/**
- * Sets the maximum size for the Application Caches content.
+ * Sets the maximum size for the Application Cache content. The passed size
+ * will be rounded to the nearest value that the database can support, so
+ * this should be viewed as a guide, not a hard limit. Setting the
+ * size to a value less than current database size does not cause the
+ * database to be trimmed. The default size is {@link Long#MAX_VALUE}.
*
* @param appCacheMaxSize the maximum size in bytes
*/
@@ -976,7 +1072,9 @@ public abstract class WebSettings {
}
/**
- * Sets whether the database storage API is enabled.
+ * Sets whether the database storage API is enabled. The default value is
+ * false. See also {@link #setDatabasePath} for how to correctly set up the
+ * database storage API.
*
* @param flag true if the WebView should use the database storage API
*/
@@ -985,7 +1083,7 @@ public abstract class WebSettings {
}
/**
- * Sets whether the DOM storage API is enabled.
+ * Sets whether the DOM storage API is enabled. The default value is false.
*
* @param flag true if the WebView should use the DOM storage API
*/
@@ -997,15 +1095,16 @@ public abstract class WebSettings {
* Gets whether the DOM Storage APIs are enabled.
*
* @return true if the DOM Storage APIs are enabled
+ * @see #setDomStorageEnabled
*/
public synchronized boolean getDomStorageEnabled() {
throw new MustOverrideException();
}
/**
- * Gets the path to where database storage API databases are saved for
- * the current WebView.
+ * Gets the path to where database storage API databases are saved.
*
* @return the String path to the database storage API databases
+ * @see #setDatabasePath
*/
public synchronized String getDatabasePath() {
throw new MustOverrideException();
@@ -1015,13 +1114,16 @@ public abstract class WebSettings {
* Gets whether the database storage API is enabled.
*
* @return true if the database storage API is enabled
+ * @see #setDatabaseEnabled
*/
public synchronized boolean getDatabaseEnabled() {
throw new MustOverrideException();
}
/**
- * Sets whether Geolocation is enabled.
+ * Sets whether Geolocation is enabled. The default is true. See also
+ * {@link #setGeolocationDatabasePath} for how to correctly set up
+ * Geolocation.
*
* @param flag whether Geolocation should be enabled
*/
@@ -1064,6 +1166,7 @@ public abstract class WebSettings {
* Gets whether plugins are enabled.
*
* @return true if plugins are enabled
+ * @see #setPluginsEnabled
* @deprecated This method has been replaced by {@link #getPluginState}
*/
@Deprecated
@@ -1072,9 +1175,10 @@ public abstract class WebSettings {
}
/**
- * Gets the current plugin state.
+ * Gets the current state regarding whether plugins are enabled.
*
- * @return a value corresponding to the enum PluginState
+ * @return the plugin state as a {@link PluginState} value
+ * @see #setPluginState
*/
public synchronized PluginState getPluginState() {
throw new MustOverrideException();
@@ -1135,8 +1239,8 @@ public abstract class WebSettings {
}
/**
- * Sets the WebView's user-agent string. If the string "ua" is null or empty,
- * it will use the system default user-agent string.
+ * Sets the WebView's user-agent string. If the string is null or empty,
+ * the system default value will be used.
*/
public synchronized void setUserAgentString(String ua) {
throw new MustOverrideException();
@@ -1144,14 +1248,29 @@ public abstract class WebSettings {
/**
* Gets the WebView's user-agent string.
+ *
+ * @return the WebView's user-agent string
+ * @see #setUserAgentString
*/
public synchronized String getUserAgentString() {
throw new MustOverrideException();
}
/**
+ * Returns the default User-Agent used by a WebView.
+ * An instance of WebView could use a different User-Agent if a call
+ * is made to {@link WebSettings#setUserAgentString(String)}.
+ *
+ * @param context a Context object used to access application assets
+ */
+ public static String getDefaultUserAgent(Context context) {
+ return WebViewFactory.getProvider().getStatics().getDefaultUserAgent(context);
+ }
+
+ /**
* Tells the WebView whether it needs to set a node to have focus when
- * {@link WebView#requestFocus(int, android.graphics.Rect)} is called.
+ * {@link WebView#requestFocus(int, android.graphics.Rect)} is called. The
+ * default value is true.
*
* @param flag whether the WebView needs to set a node
*/
@@ -1161,9 +1280,10 @@ public abstract class WebSettings {
/**
* Sets the priority of the Render thread. Unlike the other settings, this
- * one only needs to be called once per process. The default is NORMAL.
+ * one only needs to be called once per process. The default value is
+ * {@link RenderPriority#NORMAL}.
*
- * @param priority a RenderPriority
+ * @param priority the priority
*/
public synchronized void setRenderPriority(RenderPriority priority) {
throw new MustOverrideException();
@@ -1171,20 +1291,25 @@ public abstract class WebSettings {
/**
* Overrides the way the cache is used. The way the cache is used is based
- * on the navigation option. For a normal page load, the cache is checked
+ * on the navigation type. For a normal page load, the cache is checked
* and content is re-validated as needed. When navigating back, content is
- * not revalidated, instead the content is just pulled from the cache.
- * This function allows the client to override this behavior.
+ * not revalidated, instead the content is just retrieved from the cache.
+ * This method allows the client to override this behavior by specifying
+ * one of {@link #LOAD_DEFAULT}, {@link #LOAD_NORMAL},
+ * {@link #LOAD_CACHE_ELSE_NETWORK}, {@link #LOAD_NO_CACHE} or
+ * {@link #LOAD_CACHE_ONLY}. The default value is {@link #LOAD_DEFAULT}.
*
- * @param mode one of the LOAD_ values
+ * @param mode the mode to use
*/
public void setCacheMode(int mode) {
throw new MustOverrideException();
}
/**
- * Gets the current setting for overriding the cache mode. For a full
- * description, see the {@link #setCacheMode(int)} function.
+ * Gets the current setting for overriding the cache mode.
+ *
+ * @return the current setting for overriding the cache mode
+ * @see #setCacheMode
*/
public int getCacheMode() {
throw new MustOverrideException();
diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java
index 1288613..1bbe7bb 100644
--- a/core/java/android/webkit/WebSettingsClassic.java
+++ b/core/java/android/webkit/WebSettingsClassic.java
@@ -34,7 +34,7 @@ import java.util.Locale;
*/
public class WebSettingsClassic extends WebSettings {
// TODO: Keep this up to date
- private static final String PREVIOUS_VERSION = "4.0.4";
+ private static final String PREVIOUS_VERSION = "4.1.1";
// WebView associated with this WebSettings.
private WebViewClassic mWebView;
@@ -116,6 +116,7 @@ public class WebSettingsClassic extends WebSettings {
private boolean mNeedInitialFocus = true;
private boolean mNavDump = false;
private boolean mSupportZoom = true;
+ private boolean mMediaPlaybackRequiresUserGesture = true;
private boolean mBuiltInZoomControls = false;
private boolean mDisplayZoomControls = true;
private boolean mAllowFileAccess = true;
@@ -373,6 +374,21 @@ public class WebSettingsClassic extends WebSettings {
synchronized(sLockForLocaleSettings) {
locale = sLocale;
}
+ return getDefaultUserAgentForLocale(mContext, locale);
+ }
+
+ /**
+ * Returns the default User-Agent used by a WebView.
+ * An instance of WebView could use a different User-Agent if a call
+ * is made to {@link WebSettings#setUserAgent(int)} or
+ * {@link WebSettings#setUserAgentString(String)}.
+ *
+ * @param context a Context object used to access application assets
+ * @param locale The Locale to use in the User-Agent string.
+ * @see WebViewFactoryProvider#getDefaultUserAgent(Context)
+ * @see WebView#getDefaultUserAgent(Context)
+ */
+ public static String getDefaultUserAgentForLocale(Context context, Locale locale) {
StringBuffer buffer = new StringBuffer();
// Add version
final String version = Build.VERSION.RELEASE;
@@ -416,9 +432,9 @@ public class WebSettingsClassic extends WebSettings {
buffer.append(" Build/");
buffer.append(id);
}
- String mobile = mContext.getResources().getText(
+ String mobile = context.getResources().getText(
com.android.internal.R.string.web_user_agent_target_content).toString();
- final String base = mContext.getResources().getText(
+ final String base = context.getResources().getText(
com.android.internal.R.string.web_user_agent).toString();
return String.format(base, buffer, mobile);
}
@@ -459,6 +475,25 @@ public class WebSettingsClassic extends WebSettings {
}
/**
+ * @see android.webkit.WebSettings#setMediaPlaybackRequiresUserGesture(boolean)
+ */
+ @Override
+ public void setMediaPlaybackRequiresUserGesture(boolean support) {
+ if (mMediaPlaybackRequiresUserGesture != support) {
+ mMediaPlaybackRequiresUserGesture = support;
+ postSync();
+ }
+ }
+
+ /**
+ * @see android.webkit.WebSettings#getMediaPlaybackRequiresUserGesture()
+ */
+ @Override
+ public boolean getMediaPlaybackRequiresUserGesture() {
+ return mMediaPlaybackRequiresUserGesture;
+ }
+
+ /**
* @see android.webkit.WebSettings#setBuiltInZoomControls(boolean)
*/
@Override
@@ -630,34 +665,6 @@ public class WebSettingsClassic extends WebSettings {
}
/**
- * @see android.webkit.WebSettings#setTextSize(android.webkit.WebSettingsClassic.TextSize)
- */
- @Override
- public synchronized void setTextSize(TextSize t) {
- setTextZoom(t.value);
- }
-
- /**
- * @see android.webkit.WebSettings#getTextSize()
- */
- @Override
- public synchronized TextSize getTextSize() {
- TextSize closestSize = null;
- int smallestDelta = Integer.MAX_VALUE;
- for (TextSize size : TextSize.values()) {
- int delta = Math.abs(mTextSize - size.value);
- if (delta == 0) {
- return size;
- }
- if (delta < smallestDelta) {
- smallestDelta = delta;
- closestSize = size;
- }
- }
- return closestSize != null ? closestSize : TextSize.NORMAL;
- }
-
- /**
* Set the double-tap zoom of the page in percent. Default is 100.
* @param doubleTapZoom A percent value for increasing or decreasing the double-tap zoom.
*/
@@ -1115,6 +1122,7 @@ public class WebSettingsClassic extends WebSettings {
if (mJavaScriptEnabled != flag) {
mJavaScriptEnabled = flag;
postSync();
+ mWebView.updateJavaScriptEnabled(flag);
}
}
diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java
index 76674f4..1e955bd 100644
--- a/core/java/android/webkit/WebStorage.java
+++ b/core/java/android/webkit/WebStorage.java
@@ -23,17 +23,22 @@ import java.util.Map;
* {@link WebView}. It manages the Application Cache API, the Web SQL Database
* API and the HTML5 Web Storage API.
*
- * The Web SQL Database API provides storage which is private to a given
- * origin, where an origin comprises the host, scheme and port of a URI.
- * Similarly, use of the Application Cache API can be attributed to an origin.
- * This class provides access to the storage use and quotas for these APIs for
- * a given origin. Origins are represented using {@link WebStorage.Origin}.
+ * The Application Cache API provides a mechanism to create and maintain an
+ * application cache to power offline Web applications. Use of the Application
+ * Cache API can be attributed to an origin {@link WebStorage.Origin}, however
+ * it is not possible to set per-origin quotas. Note that there can be only
+ * one application cache per application.
+ *
+ * The Web SQL Database API provides storage which is private to a given origin.
+ * Similar to the Application Cache, use of the Web SQL Database can be attributed
+ * to an origin. It is also possible to set per-origin quotas.
*/
public class WebStorage {
/**
* Encapsulates a callback function which is used to provide a new quota
- * for a JavaScript storage API. See
+ * for a JavaScript storage API.
+ * See
* {@link WebChromeClient#onExceededDatabaseQuota} and
* {@link WebChromeClient#onReachedMaxAppCacheSize}.
*/
@@ -54,6 +59,7 @@ public class WebStorage {
/**
* This class encapsulates information about the amount of storage
* currently used by an origin for the JavaScript storage APIs.
+ * An origin comprises the host, scheme and port of a URI.
* See {@link WebStorage} for details.
*/
public static class Origin {
diff --git a/core/java/android/webkit/WebSyncManager.java b/core/java/android/webkit/WebSyncManager.java
index 38b5e5c..d3ec603 100644
--- a/core/java/android/webkit/WebSyncManager.java
+++ b/core/java/android/webkit/WebSyncManager.java
@@ -37,9 +37,6 @@ abstract class WebSyncManager implements Runnable {
// handler of the sync thread
protected Handler mHandler;
// database for the persistent storage
- // Note that this remains uninitialised as it is unused. We cannot remove
- // the member as it leaked into the public API via CookieSyncManager.
- // TODO: hide this member, ditto for mHandler.
protected WebViewDatabase mDataBase;
// Ref count for calls to start/stop sync
private int mStartSyncRefCount;
@@ -65,6 +62,7 @@ abstract class WebSyncManager implements Runnable {
protected WebSyncManager(Context context, String name) {
mThreadName = name;
if (context != null) {
+ mDataBase = WebViewDatabase.getInstance(context);
mSyncThread = new Thread(this);
mSyncThread.setName(mThreadName);
mSyncThread.start();
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index e493653..4202a7f 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -35,8 +35,8 @@ import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewDebug;
import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -44,6 +44,7 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.AbsoluteLayout;
+import java.io.BufferedWriter;
import java.io.File;
import java.util.Map;
@@ -163,10 +164,7 @@ import java.util.Map;
*
* <p>For obvious security reasons, your application has its own
* cache, cookie store etc.&mdash;it does not share the Browser
- * application's data. Cookies are managed on a separate thread, so
- * operations like index building don't block the UI
- * thread. Follow the instructions in {@link android.webkit.CookieSyncManager}
- * if you want to use cookies in your application.
+ * application's data.
* </p>
*
* <p>By default, requests by the HTML to open new windows are
@@ -263,7 +261,7 @@ import java.util.Map;
@Widget
public class WebView extends AbsoluteLayout
implements ViewTreeObserver.OnGlobalFocusChangeListener,
- ViewGroup.OnHierarchyChangeListener {
+ ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {
private static final String LOGTAG = "webview_proxy";
@@ -312,15 +310,15 @@ public class WebView extends AbsoluteLayout
/**
* Notifies the listener about progress made by a find operation.
*
- * @param numberOfMatches how many matches have been found
* @param activeMatchOrdinal the zero-based ordinal of the currently selected match
+ * @param numberOfMatches how many matches have been found
* @param isDoneCounting whether the find operation has actually completed. The listener
* may be notified multiple times while the
* operation is underway, and the numberOfMatches
* value should not be considered final unless
* isDoneCounting is true.
*/
- public void onFindResultReceived(int numberOfMatches, int activeMatchOrdinal,
+ public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
boolean isDoneCounting);
}
@@ -332,16 +330,12 @@ public class WebView extends AbsoluteLayout
@Deprecated
public interface PictureListener {
/**
- * Notifies the listener that the picture has changed.
+ * Used to provide notification that the WebView's picture has changed.
+ * See {@link WebView#capturePicture} for details of the picture.
*
* @param view the WebView that owns the picture
* @param picture the new picture
- * @deprecated Due to internal changes, the picture does not include
- * composited layers such as fixed position elements or
- * scrollable divs. While the PictureListener API can still
- * be used to detect changes in the WebView content, you
- * are advised against its usage until a replacement is
- * provided in a future Android release.
+ * @deprecated Deprecated due to internal changes.
*/
@Deprecated
public void onNewPicture(WebView view, Picture picture);
@@ -475,7 +469,13 @@ public class WebView extends AbsoluteLayout
* @param defStyle the default style resource ID
* @param privateBrowsing whether this WebView will be initialized in
* private mode
+ *
+ * @deprecated Private browsing is no longer supported directly via
+ * WebView and will be removed in a future release. Prefer using
+ * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
+ * and {@link WebStorage} for fine-grained control of privacy data.
*/
+ @Deprecated
public WebView(Context context, AttributeSet attrs, int defStyle,
boolean privateBrowsing) {
this(context, attrs, defStyle, null, privateBrowsing);
@@ -555,6 +555,7 @@ public class WebView extends AbsoluteLayout
* Gets the visible height (in pixels) of the embedded title bar (if any).
*
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
public int getVisibleTitleHeight() {
checkThread();
@@ -574,7 +575,11 @@ public class WebView extends AbsoluteLayout
/**
* Sets the SSL certificate for the main top-level page.
+ *
+ * @deprecated Calling this function has no useful effect, and will be
+ * ignored in future releases.
*/
+ @Deprecated
public void setCertificate(SslCertificate certificate) {
checkThread();
mProvider.setCertificate(certificate);
@@ -585,12 +590,16 @@ public class WebView extends AbsoluteLayout
//-------------------------------------------------------------------------
/**
- * Saves the username and password for a particular host in this WebView's
- * internal database.
+ * Sets a username and password pair for the specified host. This data is
+ * used by the Webview to autocomplete username and password fields in web
+ * forms. Note that this is unrelated to the credentials used for HTTP
+ * authentication.
*
* @param host the host that required the credentials
* @param username the username for the given host
* @param password the password for the given host
+ * @see WebViewDatabase#clearUsernamePassword
+ * @see WebViewDatabase#hasUsernamePassword
*/
public void savePassword(String host, String username, String password) {
checkThread();
@@ -598,13 +607,17 @@ public class WebView extends AbsoluteLayout
}
/**
- * Sets the HTTP authentication credentials for a given host and realm.
+ * Stores HTTP authentication credentials for a given host and realm. This
+ * method is intended to be used with
+ * {@link WebViewClient#onReceivedHttpAuthRequest}.
*
- * @param host the host for the credentials
- * @param realm the realm for the credentials
- * @param username the username for the password. If it is null, it means
- * password can't be saved.
+ * @param host the host to which the credentials apply
+ * @param realm the realm to which the credentials apply
+ * @param username the username
* @param password the password
+ * @see getHttpAuthUsernamePassword
+ * @see WebViewDatabase#hasHttpAuthUsernamePassword
+ * @see WebViewDatabase#clearHttpAuthUsernamePassword
*/
public void setHttpAuthUsernamePassword(String host, String realm,
String username, String password) {
@@ -613,13 +626,18 @@ public class WebView extends AbsoluteLayout
}
/**
- * Retrieves the HTTP authentication username and password for a given
- * host and realm pair
+ * Retrieves HTTP authentication credentials for a given host and realm.
+ * This method is intended to be used with
+ * {@link WebViewClient#onReceivedHttpAuthRequest}.
*
- * @param host the host for which the credentials apply
- * @param realm the realm for which the credentials apply
- * @return String[] if found. String[0] is username, which can be null and
- * String[1] is password. Return null if it can't find anything.
+ * @param host the host to which the credentials apply
+ * @param realm the realm to which the credentials apply
+ * @return the credentials as a String array, if found. The first element
+ * is the username and the second element is the password. Null if
+ * no credentials are found.
+ * @see setHttpAuthUsernamePassword
+ * @see WebViewDatabase#hasHttpAuthUsernamePassword
+ * @see WebViewDatabase#clearHttpAuthUsernamePassword
*/
public String[] getHttpAuthUsernamePassword(String host, String realm) {
checkThread();
@@ -641,6 +659,7 @@ public class WebView extends AbsoluteLayout
* Notifications are enabled by default.
*
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public static void enablePlatformNotifications() {
@@ -653,6 +672,7 @@ public class WebView extends AbsoluteLayout
* Notifications are enabled by default.
*
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public static void disablePlatformNotifications() {
@@ -677,14 +697,11 @@ public class WebView extends AbsoluteLayout
* {@link android.app.Activity#onSaveInstanceState}. Please note that this
* method no longer stores the display data for this WebView. The previous
* behavior could potentially leak files if {@link #restoreState} was never
- * called. See {@link #savePicture} and {@link #restorePicture} for saving
- * and restoring the display data.
+ * called.
*
* @param outState the Bundle to store this WebView's state
* @return the same copy of the back/forward list used to save the state. If
* saveState fails, the returned list will be null.
- * @see #savePicture
- * @see #restorePicture
*/
public WebBackForwardList saveState(Bundle outState) {
checkThread();
@@ -699,6 +716,7 @@ public class WebView extends AbsoluteLayout
* overwritten with this WebView's picture data.
* @return true if the picture was successfully saved
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public boolean savePicture(Bundle b, final File dest) {
@@ -715,6 +733,7 @@ public class WebView extends AbsoluteLayout
* @param src the file where the picture data was stored
* @return true if the picture was successfully restored
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public boolean restorePicture(Bundle b, File src) {
@@ -723,19 +742,16 @@ public class WebView extends AbsoluteLayout
}
/**
- * Restores the state of this WebView from the given map used in
- * {@link android.app.Activity#onRestoreInstanceState}. This method should
- * be called to restore the state of this WebView before using the object. If
+ * Restores the state of this WebView from the given Bundle. This method is
+ * intended for use in {@link android.app.Activity#onRestoreInstanceState}
+ * and should be called to restore the state of this WebView. If
* it is called after this WebView has had a chance to build state (load
* pages, create a back/forward list, etc.) there may be undesirable
* side-effects. Please note that this method no longer restores the
- * display data for this WebView. See {@link #savePicture} and {@link
- * #restorePicture} for saving and restoring the display data.
+ * display data for this WebView.
*
* @param inState the incoming Bundle of state
* @return the restored back/forward list or null if restoreState failed
- * @see #savePicture
- * @see #restorePicture
*/
public WebBackForwardList restoreState(Bundle inState) {
checkThread();
@@ -791,11 +807,13 @@ public class WebView extends AbsoluteLayout
* #loadDataWithBaseURL(String,String,String,String,String)
* loadDataWithBaseURL()} with an appropriate base URL.
* <p>
- * If the value of the encoding parameter is 'base64', then the data must
- * be encoded as base64. Otherwise, the data must use ASCII encoding for
+ * The encoding parameter specifies whether the data is base64 or URL
+ * encoded. If the data is base64 encoded, the value of the encoding
+ * parameter must be 'base64'. For all other values of the parameter,
+ * including null, it is assumed that the data uses ASCII encoding for
* octets inside the range of safe URL characters and use the standard %xx
- * hex encoding of URLs for octets outside that range. For example,
- * '#', '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively.
+ * hex encoding of URLs for octets outside that range. For example, '#',
+ * '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively.
* <p>
* The 'data' scheme URL formed by this method uses the default US-ASCII
* charset. If you need need to set a different charset, you should form a
@@ -986,13 +1004,18 @@ public class WebView extends AbsoluteLayout
}
/**
- * Gets a new picture that captures the current display of this WebView.
- * This is a copy of the display, and will be unaffected if this WebView
- * later loads a different URL.
+ * Gets a new picture that captures the current contents of this WebView.
+ * The picture is of the entire document being displayed, and is not
+ * limited to the area currently displayed by this WebView. Also, the
+ * picture is a static copy and is unaffected by later changes to the
+ * content being displayed.
+ * <p>
+ * Note that due to internal changes, for API levels between
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and
+ * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} inclusive, the
+ * picture does not include fixed position elements or scrollable divs.
*
- * @return a picture containing the current contents of this WebView. Note
- * this picture is of the entire document, and is not restricted to
- * the bounds of the view.
+ * @return a picture that captures the current contents of this WebView
*/
public Picture capturePicture() {
checkThread();
@@ -1003,7 +1026,13 @@ public class WebView extends AbsoluteLayout
* Gets the current scale of this WebView.
*
* @return the current scale
+ *
+ * @deprecated This method is prone to inaccuracy due to race conditions
+ * between the web rendering and UI threads; prefer
+ * {@link WebViewClient#onScaleChanged}.
*/
+ @Deprecated
+ @ViewDebug.ExportedProperty(category = "webview")
public float getScale() {
checkThread();
return mProvider.getScale();
@@ -1094,6 +1123,7 @@ public class WebView extends AbsoluteLayout
*
* @return the URL for the current page
*/
+ @ViewDebug.ExportedProperty(category = "webview")
public String getUrl() {
checkThread();
return mProvider.getUrl();
@@ -1108,6 +1138,7 @@ public class WebView extends AbsoluteLayout
*
* @return the URL that was originally requested for the current page
*/
+ @ViewDebug.ExportedProperty(category = "webview")
public String getOriginalUrl() {
checkThread();
return mProvider.getOriginalUrl();
@@ -1119,6 +1150,7 @@ public class WebView extends AbsoluteLayout
*
* @return the title for the current page
*/
+ @ViewDebug.ExportedProperty(category = "webview")
public String getTitle() {
checkThread();
return mProvider.getTitle();
@@ -1161,6 +1193,7 @@ public class WebView extends AbsoluteLayout
*
* @return the height of the HTML content
*/
+ @ViewDebug.ExportedProperty(category = "webview")
public int getContentHeight() {
checkThread();
return mProvider.getContentHeight();
@@ -1172,6 +1205,7 @@ public class WebView extends AbsoluteLayout
* @return the width of the HTML content
* @hide
*/
+ @ViewDebug.ExportedProperty(category = "webview")
public int getContentWidth() {
return mProvider.getContentWidth();
}
@@ -1246,8 +1280,10 @@ public class WebView extends AbsoluteLayout
}
/**
- * Makes sure that clearing the form data removes the adapter from the
- * currently focused textfield if there is one.
+ * Removes the autocomplete popup from the currently focused form field, if
+ * present. Note this only affects the display of the autocomplete popup,
+ * it does not remove any saved form data from this WebView's store. To do
+ * that, use {@link WebViewDatabase#clearFormData}.
*/
public void clearFormData() {
checkThread();
@@ -1297,12 +1333,11 @@ public class WebView extends AbsoluteLayout
}
/**
- * Highlights and scrolls to the next match found by {@link #findAll} or
+ * Highlights and scrolls to the next match found by
* {@link #findAllAsync}, wrapping around page boundaries as necessary.
- * Notifies any registered {@link FindListener}. If neither
- * {@link #findAll} nor {@link #findAllAsync(String)} has been called yet,
- * or if {@link #clearMatches} has been called since the last find
- * operation, this function does nothing.
+ * Notifies any registered {@link FindListener}. If {@link #findAllAsync(String)}
+ * has not been called yet, or if {@link #clearMatches} has been called since the
+ * last find operation, this function does nothing.
*
* @param forward the direction to search
* @see #setFindListener
@@ -1331,8 +1366,7 @@ public class WebView extends AbsoluteLayout
/**
* Finds all instances of find on the page and highlights them,
* asynchronously. Notifies any registered {@link FindListener}.
- * Successive calls to this or {@link #findAll} will cancel any
- * pending searches.
+ * Successive calls to this will cancel any pending searches.
*
* @param find the string to find.
* @see #setFindListener
@@ -1381,13 +1415,12 @@ public class WebView extends AbsoluteLayout
* @return the address, or if no address is found, null
*/
public static String findAddress(String addr) {
- checkThread();
return getFactory().getStatics().findAddress(addr);
}
/**
* Clears the highlighting surrounding text matches created by
- * {@link #findAll} or {@link #findAllAsync}.
+ * {@link #findAllAsync}.
*/
public void clearMatches() {
checkThread();
@@ -1447,6 +1480,7 @@ public class WebView extends AbsoluteLayout
*
* @param listener an implementation of WebView.PictureListener
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public void setPictureListener(PictureListener listener) {
@@ -1457,10 +1491,22 @@ public class WebView extends AbsoluteLayout
/**
* Injects the supplied Java object into this WebView. The object is
* injected into the JavaScript context of the main frame, using the
- * supplied name. This allows the Java object's public methods to be
- * accessed from JavaScript. Note that that injected objects will not
+ * supplied name. This allows the Java object's methods to be
+ * accessed from JavaScript. For applications targeted to API
+ * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ * and above, only public methods that are annotated with
+ * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript.
+ * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
+ * all public methods (including the inherited ones) can be accessed, see the
+ * important security note below for implications.
+ * <p> Note that injected objects will not
* appear in JavaScript until the page is next (re)loaded. For example:
- * <pre> webView.addJavascriptInterface(new Object(), "injectedObject");
+ * <pre>
+ * class JsObject {
+ * {@literal @}JavascriptInterface
+ * public String toString() { return "injectedObject"; }
+ * }
+ * webView.addJavascriptInterface(new JsObject(), "injectedObject");
* webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
* webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
* <p>
@@ -1468,7 +1514,9 @@ public class WebView extends AbsoluteLayout
* <ul>
* <li> This method can be used to allow JavaScript to control the host
* application. This is a powerful feature, but also presents a security
- * risk, particularly as JavaScript could use reflection to access an
+ * risk for applications targeted to API level
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below, because
+ * JavaScript could use reflection to access an
* injected object's public fields. Use of this method in a WebView
* containing untrusted content could allow an attacker to manipulate the
* host application in unintended ways, executing Java code with the
@@ -1477,6 +1525,7 @@ public class WebView extends AbsoluteLayout
* <li> JavaScript interacts with Java object on a private, background
* thread of this WebView. Care is therefore required to maintain thread
* safety.</li>
+ * <li> The Java object's fields are not accessible.</li>
* </ul>
*
* @param object the Java object to inject into this WebView's JavaScript
@@ -1539,11 +1588,11 @@ public class WebView extends AbsoluteLayout
* functionality; it will be deprecated in the future.
*
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public void emulateShiftHeld() {
checkThread();
- mProvider.emulateShiftHeld();
}
/**
@@ -1574,6 +1623,10 @@ public class WebView extends AbsoluteLayout
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
}
+ /**
+ * @deprecated Only the default case, true, will be supported in a future version.
+ */
+ @Deprecated
public void setMapTrackballToArrowKeys(boolean setMap) {
checkThread();
mProvider.setMapTrackballToArrowKeys(setMap);
@@ -1607,7 +1660,12 @@ public class WebView extends AbsoluteLayout
* Gets whether this WebView can be zoomed in.
*
* @return true if this WebView can be zoomed in
+ *
+ * @deprecated This method is prone to inaccuracy due to race conditions
+ * between the web rendering and UI threads; prefer
+ * {@link WebViewClient#onScaleChanged}.
*/
+ @Deprecated
public boolean canZoomIn() {
checkThread();
return mProvider.canZoomIn();
@@ -1617,7 +1675,12 @@ public class WebView extends AbsoluteLayout
* Gets whether this WebView can be zoomed out.
*
* @return true if this WebView can be zoomed out
+ *
+ * @deprecated This method is prone to inaccuracy due to race conditions
+ * between the web rendering and UI threads; prefer
+ * {@link WebViewClient#onScaleChanged}.
*/
+ @Deprecated
public boolean canZoomOut() {
checkThread();
return mProvider.canZoomOut();
@@ -1645,11 +1708,29 @@ public class WebView extends AbsoluteLayout
/**
* @deprecated This method is now obsolete.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
*/
@Deprecated
public void debugDump() {
checkThread();
- mProvider.debugDump();
+ }
+
+ /**
+ * See {@link ViewDebug.HierarchyHandler#dumpViewHierarchyWithProperties(BufferedWriter, int)}
+ * @hide
+ */
+ @Override
+ public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) {
+ mProvider.dumpViewHierarchyWithProperties(out, level);
+ }
+
+ /**
+ * See {@link ViewDebug.HierarchyHandler#findHierarchyView(String, int)}
+ * @hide
+ */
+ @Override
+ public View findHierarchyView(String className, int hashCode) {
+ return mProvider.findHierarchyView(className, hashCode);
}
//-------------------------------------------------------------------------
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index bcab1b1..d68511c 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -16,6 +16,7 @@
package android.webkit;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.animation.ObjectAnimator;
import android.annotation.Widget;
import android.app.ActivityManager;
@@ -26,7 +27,6 @@ import android.content.ClipboardManager;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
@@ -55,6 +55,7 @@ import android.net.ProxyProperties;
import android.net.Uri;
import android.net.http.SslCertificate;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -68,7 +69,6 @@ import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
-import android.view.Display;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.HardwareCanvas;
@@ -86,7 +86,6 @@ import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewRootImpl;
-import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -117,6 +116,8 @@ import android.widget.Toast;
import junit.framework.Assert;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -129,11 +130,10 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* Implements a backend provider for the {@link WebView} public API.
@@ -275,7 +275,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
super.setComposingText(limitedText, newCursorPosition);
updateSelection();
if (limitedText != text) {
- restartInput();
int lastCaret = start + limitedText.length();
finishComposingText();
setSelection(lastCaret, lastCaret);
@@ -376,28 +375,26 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
}
}
+ int action = EditorInfo.IME_ACTION_GO;
switch (type) {
case WebTextView.NORMAL_TEXT_FIELD:
- imeOptions |= EditorInfo.IME_ACTION_GO;
break;
case WebTextView.TEXT_AREA:
inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE
| InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
| InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
- imeOptions |= EditorInfo.IME_ACTION_NONE;
+ action = EditorInfo.IME_ACTION_NONE;
break;
case WebTextView.PASSWORD:
inputType |= EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
- imeOptions |= EditorInfo.IME_ACTION_GO;
break;
case WebTextView.SEARCH:
- imeOptions |= EditorInfo.IME_ACTION_SEARCH;
+ action = EditorInfo.IME_ACTION_SEARCH;
break;
case WebTextView.EMAIL:
// inputType needs to be overwritten because of the different text variation.
inputType = InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
- imeOptions |= EditorInfo.IME_ACTION_GO;
break;
case WebTextView.NUMBER:
// inputType needs to be overwritten because of the different class.
@@ -405,23 +402,20 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
| InputType.TYPE_NUMBER_FLAG_SIGNED | InputType.TYPE_NUMBER_FLAG_DECIMAL;
// Number and telephone do not have both a Tab key and an
// action, so set the action to NEXT
- imeOptions |= EditorInfo.IME_ACTION_NEXT;
break;
case WebTextView.TELEPHONE:
// inputType needs to be overwritten because of the different class.
inputType = InputType.TYPE_CLASS_PHONE;
- imeOptions |= EditorInfo.IME_ACTION_NEXT;
break;
case WebTextView.URL:
// TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so
// exclude it for now.
- imeOptions |= EditorInfo.IME_ACTION_GO;
inputType |= InputType.TYPE_TEXT_VARIATION_URI;
break;
default:
- imeOptions |= EditorInfo.IME_ACTION_GO;
break;
}
+ imeOptions |= action;
mHint = initData.mLabel;
mInputType = inputType;
mImeOptions = imeOptions;
@@ -679,6 +673,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// after resize.
static private final int EDIT_RECT_BUFFER = 10;
+ static private final long SELECTION_HANDLE_ANIMATION_MS = 150;
+
// true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
private boolean mAutoRedraw;
@@ -742,12 +738,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// the existing GL resources for the html5 video will be destroyed
// at native side.
// Here we just need to clean up the Surface Texture which is static.
- if (level >= TRIM_MEMORY_UI_HIDDEN) {
+ if (level > TRIM_MEMORY_UI_HIDDEN) {
HTML5VideoInline.cleanupSurfaceTexture();
+ HTML5VideoView.release();
}
WebViewClassic.nativeOnTrimMemory(level);
}
-
}
// A final CallbackProxy shared by WebViewCore and BrowserFrame.
@@ -949,21 +945,20 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private Drawable mSelectHandleLeft;
private Drawable mSelectHandleRight;
private Drawable mSelectHandleCenter;
- private Point mSelectHandleLeftOffset;
- private Point mSelectHandleRightOffset;
- private Point mSelectHandleCenterOffset;
- private Point mSelectCursorLeft = new Point();
- private int mSelectCursorLeftLayerId;
- private QuadF mSelectCursorLeftTextQuad = new QuadF();
- private Point mSelectCursorRight = new Point();
- private int mSelectCursorRightLayerId;
- private QuadF mSelectCursorRightTextQuad = new QuadF();
+ private Point mSelectOffset;
+ private Point mSelectCursorBase = new Point();
+ private Rect mSelectHandleBaseBounds = new Rect();
+ private int mSelectCursorBaseLayerId;
+ private QuadF mSelectCursorBaseTextQuad = new QuadF();
+ private Point mSelectCursorExtent = new Point();
+ private Rect mSelectHandleExtentBounds = new Rect();
+ private int mSelectCursorExtentLayerId;
+ private QuadF mSelectCursorExtentTextQuad = new QuadF();
private Point mSelectDraggingCursor;
- private Point mSelectDraggingOffset;
private QuadF mSelectDraggingTextQuad;
private boolean mIsCaretSelection;
- static final int HANDLE_ID_LEFT = 0;
- static final int HANDLE_ID_RIGHT = 1;
+ static final int HANDLE_ID_BASE = 0;
+ static final int HANDLE_ID_EXTENT = 1;
// the color used to highlight the touch rectangles
static final int HIGHLIGHT_COLOR = 0x6633b5e5;
@@ -1037,7 +1032,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
static final int AUTOFILL_COMPLETE = 134;
static final int SCREEN_ON = 136;
- static final int ENTER_FULLSCREEN_VIDEO = 137;
static final int UPDATE_ZOOM_DENSITY = 139;
static final int EXIT_FULLSCREEN_VIDEO = 140;
@@ -1053,6 +1047,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
static final int EDIT_TEXT_SIZE_CHANGED = 150;
static final int SHOW_CARET_HANDLE = 151;
static final int UPDATE_CONTENT_BOUNDS = 152;
+ static final int SCROLL_HANDLE_INTO_VIEW = 153;
private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
@@ -1313,6 +1308,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
public WebViewDatabase getWebViewDatabase(Context context) {
return WebViewDatabaseClassic.getInstance(context);
}
+
+ @Override
+ public String getDefaultUserAgent(Context context) {
+ return WebSettingsClassic.getDefaultUserAgentForLocale(context,
+ Locale.getDefault());
+ }
}
private void onHandleUiEvent(MotionEvent event, int eventType, int flags) {
@@ -1646,6 +1647,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mZoomManager.updateMultiTouchSupport(context);
}
+ void updateJavaScriptEnabled(boolean enabled) {
+ if (isAccessibilityInjectionEnabled()) {
+ getAccessibilityInjector().updateJavaScriptEnabled(enabled);
+ }
+ }
+
private void init() {
OnTrimMemoryListener.init(mContext);
mWebView.setWillNotDraw(false);
@@ -1657,7 +1664,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mTouchSlopSquare = slop * slop;
slop = configuration.getScaledDoubleTapSlop();
mDoubleTapSlopSquare = slop * slop;
- final float density = mContext.getResources().getDisplayMetrics().density;
+ final float density = WebViewCore.getFixedDisplayDensity(mContext);
// use one line height, 16 based on our current default font, for how
// far we allow a touch be away from the edge of a link
mNavSlop = (int) (16 * density);
@@ -1759,8 +1766,21 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0));
}
- private boolean isAccessibilityEnabled() {
- return AccessibilityManager.getInstance(mContext).isEnabled();
+ private boolean isAccessibilityInjectionEnabled() {
+ final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+ if (!manager.isEnabled()) {
+ return false;
+ }
+
+ // Accessibility scripts should be injected only when a speaking service
+ // is enabled. This may need to change later to accommodate Braille.
+ final List<AccessibilityServiceInfo> services = manager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_SPOKEN);
+ if (services.isEmpty()) {
+ return false;
+ }
+
+ return true;
}
private AccessibilityInjector getAccessibilityInjector() {
@@ -1789,7 +1809,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
/* package */ void adjustDefaultZoomDensity(int zoomDensity) {
- final float density = mContext.getResources().getDisplayMetrics().density
+ final float density = WebViewCore.getFixedDisplayDensity(mContext)
* 100 / zoomDensity;
updateDefaultZoomDensity(density);
}
@@ -1866,9 +1886,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mSavePasswordDialog = null;
}
})
- .setOnCancelListener(new OnCancelListener() {
+ .setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
- public void onCancel(DialogInterface dialog) {
+ public void onDismiss(DialogInterface dialog) {
if (mResumeMsg != null) {
resumeMsg.sendToTarget();
mResumeMsg = null;
@@ -2073,14 +2093,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
hideSoftKeyboard();
clearActionModes();
dismissFullScreenMode();
- cancelSelectDialog();
+ cancelDialogs();
}
- private void cancelSelectDialog() {
+ private void cancelDialogs() {
if (mListBoxDialog != null) {
mListBoxDialog.cancel();
mListBoxDialog = null;
}
+ if (mSavePasswordDialog != null) {
+ mSavePasswordDialog.dismiss();
+ mSavePasswordDialog = null;
+ }
}
/**
@@ -2108,15 +2132,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private void destroyJava() {
mCallbackProxy.blockMessages();
- clearHelpers();
- if (mListBoxDialog != null) {
- mListBoxDialog.dismiss();
- mListBoxDialog = null;
- }
- if (mSavePasswordDialog != null) {
- mSavePasswordDialog.dismiss();
- mSavePasswordDialog = null;
- }
if (mWebViewCore != null) {
// Tell WebViewCore to destroy itself
synchronized (this) {
@@ -2223,7 +2238,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
// We grab a copy of the back/forward list because a client of WebView
// may have invalidated the history list by calling clearHistory.
- WebBackForwardList list = copyBackForwardList();
+ WebBackForwardListClassic list = copyBackForwardList();
final int currentIndex = list.getCurrentIndex();
final int size = list.getSize();
// We should fail saving the state if the list is empty or the index is
@@ -2237,7 +2252,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// arrays.
ArrayList<byte[]> history = new ArrayList<byte[]>(size);
for (int i = 0; i < size; i++) {
- WebHistoryItem item = list.getItemAtIndex(i);
+ WebHistoryItemClassic item = list.getItemAtIndex(i);
if (null == item) {
// FIXME: this shouldn't happen
// need to determine how item got set to null
@@ -2436,7 +2451,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
*/
@Override
public WebBackForwardList restoreState(Bundle inState) {
- WebBackForwardList returnList = null;
+ WebBackForwardListClassic returnList = null;
if (inState == null) {
return returnList;
}
@@ -2444,7 +2459,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mCertificate = SslCertificate.restoreState(
inState.getBundle("certificate"));
- final WebBackForwardList list = mCallbackProxy.getBackForwardList();
+ final WebBackForwardListClassic list = mCallbackProxy.getBackForwardList();
final int index = inState.getInt("index");
// We can't use a clone of the list because we need to modify the
// shared copy, so synchronize instead to prevent concurrent
@@ -2465,7 +2480,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// the item and thus our history list cannot be rebuilt.
return null;
}
- WebHistoryItem item = new WebHistoryItem(data);
+ WebHistoryItem item = new WebHistoryItemClassic(data);
list.addHistoryItem(item);
}
// Grab the most recent copy to return to the caller.
@@ -2638,7 +2653,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
*/
@Override
public boolean canGoBack() {
- WebBackForwardList l = mCallbackProxy.getBackForwardList();
+ WebBackForwardListClassic l = mCallbackProxy.getBackForwardList();
synchronized (l) {
if (l.getClearPending()) {
return false;
@@ -2661,7 +2676,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
*/
@Override
public boolean canGoForward() {
- WebBackForwardList l = mCallbackProxy.getBackForwardList();
+ WebBackForwardListClassic l = mCallbackProxy.getBackForwardList();
synchronized (l) {
if (l.getClearPending()) {
return false;
@@ -2684,7 +2699,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
*/
@Override
public boolean canGoBackOrForward(int steps) {
- WebBackForwardList l = mCallbackProxy.getBackForwardList();
+ WebBackForwardListClassic l = mCallbackProxy.getBackForwardList();
synchronized (l) {
if (l.getClearPending()) {
return false;
@@ -3332,6 +3347,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
scrollLayerTo(scrollX, scrollY);
+ animateHandles();
return;
}
mInOverScrollMode = false;
@@ -3352,6 +3368,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mWebViewPrivate.super_scrollTo(scrollX, scrollY);
+ animateHandles();
+
if (mOverScrollGlow != null) {
mOverScrollGlow.pullGlow(getScrollX(), getScrollY(), oldX, oldY, maxX, maxY);
}
@@ -3398,7 +3416,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
*/
@Override
public String getTouchIconUrl() {
- WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
+ WebHistoryItemClassic h = mCallbackProxy.getBackForwardList().getCurrentItem();
return h != null ? h.getTouchIconUrl() : null;
}
@@ -3464,7 +3482,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
nativeSetPauseDrawing(mNativeClass, true);
}
- cancelSelectDialog();
+ cancelDialogs();
WebCoreThreadWatchdog.pause();
}
}
@@ -3562,7 +3580,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
* See {@link WebView#copyBackForwardList()}
*/
@Override
- public WebBackForwardList copyBackForwardList() {
+ public WebBackForwardListClassic copyBackForwardList() {
return mCallbackProxy.getBackForwardList().clone();
}
@@ -3570,7 +3588,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
* See {@link WebView#setFindListener(WebView.FindListener)}.
* @hide
*/
- public void setFindListener(WebView.FindListener listener) {
+ @Override
+ public void setFindListener(WebView.FindListener listener) {
mFindListener = listener;
}
@@ -3593,6 +3612,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
return findAllBody(find, false);
}
+ @Override
public void findAllAsync(String find) {
findAllBody(find, true);
}
@@ -3631,6 +3651,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
* If false and text is non-null, perform a find all.
* @return boolean True if the find dialog is shown, false otherwise.
*/
+ @Override
public boolean showFindDialog(String text, boolean showIme) {
FindActionModeCallback callback = new FindActionModeCallback(mContext);
if (mWebView.getParent() == null || mWebView.startActionMode(callback) == null) {
@@ -3846,17 +3867,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
return;
}
if (mSelectingText) {
- if (mSelectCursorLeftLayerId == mCurrentScrollingLayerId) {
- mSelectCursorLeft.offset(dx, dy);
- mSelectCursorLeftTextQuad.offset(dx, dy);
+ if (mSelectCursorBaseLayerId == mCurrentScrollingLayerId) {
+ mSelectCursorBase.offset(dx, dy);
+ mSelectCursorBaseTextQuad.offset(dx, dy);
}
- if (mSelectCursorRightLayerId == mCurrentScrollingLayerId) {
- mSelectCursorRight.offset(dx, dy);
- mSelectCursorRightTextQuad.offset(dx, dy);
+ if (mSelectCursorExtentLayerId == mCurrentScrollingLayerId) {
+ mSelectCursorExtent.offset(dx, dy);
+ mSelectCursorExtentTextQuad.offset(dx, dy);
}
- } else if (mHandleAlpha.getAlpha() > 0) {
- // stop fading as we're not going to move with the layer.
- mHandleAlphaAnimator.end();
}
if (mAutoCompletePopup != null &&
mCurrentScrollingLayerId == mEditTextLayerId) {
@@ -3951,7 +3969,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// reset the flag since we set to true in if need after
// loading is see onPageFinished(Url)
- if (isAccessibilityEnabled()) {
+ if (isAccessibilityInjectionEnabled()) {
getAccessibilityInjector().onPageStarted(url);
}
@@ -3966,7 +3984,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
/* package */ void onPageFinished(String url) {
mZoomManager.onPageFinished(url);
- if (isAccessibilityEnabled()) {
+ if (isAccessibilityInjectionEnabled()) {
getAccessibilityInjector().onPageFinished(url);
}
}
@@ -4097,12 +4115,22 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
*/
@Override
public void addJavascriptInterface(Object object, String name) {
+
if (object == null) {
return;
}
WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
+
arg.mObject = object;
arg.mInterfaceName = name;
+
+ // starting with JELLY_BEAN_MR1, annotations are mandatory for enabling access to
+ // methods that are accessible from JS.
+ if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ arg.mRequireAnnotation = true;
+ } else {
+ arg.mRequireAnnotation = false;
+ }
mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
}
@@ -4472,9 +4500,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
private void onZoomAnimationStart() {
- if (!mSelectingText && mHandleAlpha.getAlpha() > 0) {
- mHandleAlphaAnimator.end();
- }
}
private void onZoomAnimationEnd() {
@@ -4507,34 +4532,63 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private class SelectionHandleAlpha {
private int mAlpha = 0;
+ private int mTargetAlpha = 0;
+
public void setAlpha(int alpha) {
mAlpha = alpha;
- if (mSelectHandleCenter != null) {
- mSelectHandleCenter.setAlpha(alpha);
- mSelectHandleLeft.setAlpha(alpha);
- mSelectHandleRight.setAlpha(alpha);
- // TODO: Use partial invalidate
- invalidate();
- }
+ // TODO: Use partial invalidate
+ invalidate();
}
public int getAlpha() {
return mAlpha;
}
+ public void setTargetAlpha(int alpha) {
+ mTargetAlpha = alpha;
+ }
+
+ public int getTargetAlpha() {
+ return mTargetAlpha;
+ }
+
}
private void startSelectingText() {
mSelectingText = true;
mShowTextSelectionExtra = true;
- mHandleAlphaAnimator.setIntValues(255);
- mHandleAlphaAnimator.start();
+ animateHandles();
+ }
+
+ private void animateHandle(boolean canShow, ObjectAnimator animator,
+ Point selectionPoint, int selectionLayerId,
+ SelectionHandleAlpha alpha) {
+ boolean isVisible = canShow && mSelectingText
+ && ((mSelectionStarted && mSelectDraggingCursor == selectionPoint)
+ || isHandleVisible(selectionPoint, selectionLayerId));
+ int targetValue = isVisible ? 255 : 0;
+ if (targetValue != alpha.getTargetAlpha()) {
+ alpha.setTargetAlpha(targetValue);
+ animator.setIntValues(targetValue);
+ animator.setDuration(SELECTION_HANDLE_ANIMATION_MS);
+ animator.start();
+ }
}
+
+ private void animateHandles() {
+ boolean canShowBase = mSelectingText;
+ boolean canShowExtent = mSelectingText && !mIsCaretSelection;
+ animateHandle(canShowBase, mBaseHandleAlphaAnimator, mSelectCursorBase,
+ mSelectCursorBaseLayerId, mBaseAlpha);
+ animateHandle(canShowExtent, mExtentHandleAlphaAnimator,
+ mSelectCursorExtent, mSelectCursorExtentLayerId,
+ mExtentAlpha);
+ }
+
private void endSelectingText() {
mSelectingText = false;
mShowTextSelectionExtra = false;
- mHandleAlphaAnimator.setIntValues(0);
- mHandleAlphaAnimator.start();
+ animateHandles();
}
private void ensureSelectionHandles() {
@@ -4545,55 +4599,76 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
com.android.internal.R.drawable.text_select_handle_left).mutate();
mSelectHandleRight = mContext.getResources().getDrawable(
com.android.internal.R.drawable.text_select_handle_right).mutate();
- mHandleAlpha.setAlpha(mHandleAlpha.getAlpha());
- mSelectHandleCenterOffset = new Point(0,
- -mSelectHandleCenter.getIntrinsicHeight());
- mSelectHandleLeftOffset = new Point(0,
+ // All handles have the same height, so we can save effort with
+ // this assumption.
+ mSelectOffset = new Point(0,
-mSelectHandleLeft.getIntrinsicHeight());
- mSelectHandleRightOffset = new Point(
- -mSelectHandleLeft.getIntrinsicWidth() / 2,
- -mSelectHandleRight.getIntrinsicHeight());
}
}
+ private void drawHandle(Point point, int handleId, Rect bounds,
+ int alpha, Canvas canvas) {
+ int offset;
+ int width;
+ int height;
+ Drawable drawable;
+ boolean isLeft = nativeIsHandleLeft(mNativeClass, handleId);
+ if (isLeft) {
+ drawable = mSelectHandleLeft;
+ width = mSelectHandleLeft.getIntrinsicWidth();
+ height = mSelectHandleLeft.getIntrinsicHeight();
+ // Magic formula copied from TextView
+ offset = (width * 3) / 4;
+ } else {
+ drawable = mSelectHandleRight;
+ width = mSelectHandleRight.getIntrinsicWidth();
+ height = mSelectHandleRight.getIntrinsicHeight();
+ // Magic formula copied from TextView
+ offset = width / 4;
+ }
+ int x = contentToViewDimension(point.x);
+ int y = contentToViewDimension(point.y);
+ bounds.set(x - offset, y, x - offset + width, y + height);
+ drawable.setBounds(bounds);
+ drawable.setAlpha(alpha);
+ drawable.draw(canvas);
+ }
+
private void drawTextSelectionHandles(Canvas canvas) {
- if (mHandleAlpha.getAlpha() == 0) {
+ if (mBaseAlpha.getAlpha() == 0 && mExtentAlpha.getAlpha() == 0) {
return;
}
ensureSelectionHandles();
- if (mSelectingText) {
- int[] handles = new int[4];
- getSelectionHandles(handles);
- int start_x = contentToViewDimension(handles[0]);
- int start_y = contentToViewDimension(handles[1]);
- int end_x = contentToViewDimension(handles[2]);
- int end_y = contentToViewDimension(handles[3]);
-
- if (mIsCaretSelection) {
- // Caret handle is centered
- start_x -= (mSelectHandleCenter.getIntrinsicWidth() / 2);
- mSelectHandleCenter.setBounds(start_x, start_y,
- start_x + mSelectHandleCenter.getIntrinsicWidth(),
- start_y + mSelectHandleCenter.getIntrinsicHeight());
- } else {
- // Magic formula copied from TextView
- start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4;
- mSelectHandleLeft.setBounds(start_x, start_y,
- start_x + mSelectHandleLeft.getIntrinsicWidth(),
- start_y + mSelectHandleLeft.getIntrinsicHeight());
- end_x -= mSelectHandleRight.getIntrinsicWidth() / 4;
- mSelectHandleRight.setBounds(end_x, end_y,
- end_x + mSelectHandleRight.getIntrinsicWidth(),
- end_y + mSelectHandleRight.getIntrinsicHeight());
- }
- }
-
if (mIsCaretSelection) {
+ // Caret handle is centered
+ int x = contentToViewDimension(mSelectCursorBase.x) -
+ (mSelectHandleCenter.getIntrinsicWidth() / 2);
+ int y = contentToViewDimension(mSelectCursorBase.y);
+ mSelectHandleBaseBounds.set(x, y,
+ x + mSelectHandleCenter.getIntrinsicWidth(),
+ y + mSelectHandleCenter.getIntrinsicHeight());
+ mSelectHandleCenter.setBounds(mSelectHandleBaseBounds);
+ mSelectHandleCenter.setAlpha(mBaseAlpha.getAlpha());
mSelectHandleCenter.draw(canvas);
} else {
- mSelectHandleLeft.draw(canvas);
- mSelectHandleRight.draw(canvas);
+ drawHandle(mSelectCursorBase, HANDLE_ID_BASE,
+ mSelectHandleBaseBounds, mBaseAlpha.getAlpha(), canvas);
+ drawHandle(mSelectCursorExtent, HANDLE_ID_EXTENT,
+ mSelectHandleExtentBounds, mExtentAlpha.getAlpha(), canvas);
+ }
+ }
+
+ private boolean isHandleVisible(Point selectionPoint, int layerId) {
+ boolean isVisible = true;
+ if (mIsEditingText) {
+ isVisible = mEditTextContentBounds.contains(selectionPoint.x,
+ selectionPoint.y);
}
+ if (isVisible) {
+ isVisible = nativeIsPointVisible(mNativeClass, layerId,
+ selectionPoint.x, selectionPoint.y);
+ }
+ return isVisible;
}
/**
@@ -4601,10 +4676,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
* startX, startY, endX, endY
*/
private void getSelectionHandles(int[] handles) {
- handles[0] = mSelectCursorLeft.x;
- handles[1] = mSelectCursorLeft.y;
- handles[2] = mSelectCursorRight.x;
- handles[3] = mSelectCursorRight.y;
+ handles[0] = mSelectCursorBase.x;
+ handles[1] = mSelectCursorBase.y;
+ handles[2] = mSelectCursorExtent.x;
+ handles[3] = mSelectCursorExtent.y;
}
// draw history
@@ -4839,6 +4914,43 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
/**
+ * Sets use of the Geolocation mock client. Also resets that client. Called
+ * by DRT on UI thread, need to proxy to WebCore thread.
+ *
+ * debug only
+ */
+ public void setUseMockGeolocation() {
+ mWebViewCore.sendMessage(EventHub.SET_USE_MOCK_GEOLOCATION);
+ }
+
+ /**
+ * Called by DRT on WebCore thread.
+ *
+ * debug only
+ */
+ public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) {
+ mWebViewCore.setMockGeolocationPosition(latitude, longitude, accuracy);
+ }
+
+ /**
+ * Called by DRT on WebCore thread.
+ *
+ * debug only
+ */
+ public void setMockGeolocationError(int code, String message) {
+ mWebViewCore.setMockGeolocationError(code, message);
+ }
+
+ /**
+ * Called by DRT on WebCore thread.
+ *
+ * debug only
+ */
+ public void setMockGeolocationPermission(boolean allow) {
+ mWebViewCore.setMockGeolocationPermission(allow);
+ }
+
+ /**
* Called by DRT on WebCore thread.
*
* debug only
@@ -4923,7 +5035,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
// See if the accessibility injector needs to handle this event.
- if (isAccessibilityEnabled()
+ if (isAccessibilityInjectionEnabled()
&& getAccessibilityInjector().handleKeyEventIfNecessary(event)) {
return true;
}
@@ -5030,7 +5142,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
// See if the accessibility injector needs to handle this event.
- if (isAccessibilityEnabled()
+ if (isAccessibilityInjectionEnabled()
&& getAccessibilityInjector().handleKeyEventIfNecessary(event)) {
return true;
}
@@ -5071,8 +5183,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
ClipboardManager cm = (ClipboardManager)(mContext
.getSystemService(Context.CLIPBOARD_SERVICE));
if (cm.hasPrimaryClip()) {
- Point cursorPoint = new Point(contentToViewX(mSelectCursorLeft.x),
- contentToViewY(mSelectCursorLeft.y));
+ Point cursorPoint = new Point(contentToViewX(mSelectCursorBase.x),
+ contentToViewY(mSelectCursorBase.y));
Point cursorTop = calculateCaretTop();
cursorTop.set(contentToViewX(cursorTop.x),
contentToViewY(cursorTop.y));
@@ -5122,12 +5234,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
* calculates the top of a caret.
*/
private Point calculateCaretTop() {
- float scale = scaleAlongSegment(mSelectCursorLeft.x, mSelectCursorLeft.y,
- mSelectCursorLeftTextQuad.p4, mSelectCursorLeftTextQuad.p3);
+ float scale = scaleAlongSegment(mSelectCursorBase.x, mSelectCursorBase.y,
+ mSelectCursorBaseTextQuad.p4, mSelectCursorBaseTextQuad.p3);
int x = Math.round(scaleCoordinate(scale,
- mSelectCursorLeftTextQuad.p1.x, mSelectCursorLeftTextQuad.p2.x));
+ mSelectCursorBaseTextQuad.p1.x, mSelectCursorBaseTextQuad.p2.x));
int y = Math.round(scaleCoordinate(scale,
- mSelectCursorLeftTextQuad.p1.y, mSelectCursorLeftTextQuad.p2.y));
+ mSelectCursorBaseTextQuad.p1.y, mSelectCursorBaseTextQuad.p2.y));
return new Point(x, y);
}
@@ -5138,50 +5250,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
private void syncSelectionCursors() {
- mSelectCursorLeftLayerId =
- nativeGetHandleLayerId(mNativeClass, HANDLE_ID_LEFT,
- mSelectCursorLeft, mSelectCursorLeftTextQuad);
- mSelectCursorRightLayerId =
- nativeGetHandleLayerId(mNativeClass, HANDLE_ID_RIGHT,
- mSelectCursorRight, mSelectCursorRightTextQuad);
- }
-
- private void adjustSelectionCursors() {
- if (mIsCaretSelection) {
- syncSelectionCursors();
- return; // no need to swap left and right handles.
- }
-
- boolean wasDraggingLeft = (mSelectDraggingCursor == mSelectCursorLeft);
- int oldX = mSelectDraggingCursor.x;
- int oldY = mSelectDraggingCursor.y;
- int oldLeftX = mSelectCursorLeft.x;
- int oldLeftY = mSelectCursorLeft.y;
- int oldRightX = mSelectCursorRight.x;
- int oldRightY = mSelectCursorRight.y;
- syncSelectionCursors();
-
- boolean rightChanged = (oldRightX != mSelectCursorRight.x
- || oldRightY != mSelectCursorRight.y);
- boolean leftChanged = (oldLeftX != mSelectCursorLeft.x
- || oldLeftY != mSelectCursorLeft.y);
- if (leftChanged && rightChanged) {
- // Left and right switched places, so swap dragging cursor
- boolean draggingLeft = !wasDraggingLeft;
- mSelectDraggingCursor = (draggingLeft
- ? mSelectCursorLeft : mSelectCursorRight);
- mSelectDraggingTextQuad = (draggingLeft
- ? mSelectCursorLeftTextQuad : mSelectCursorRightTextQuad);
- mSelectDraggingOffset = (draggingLeft
- ? mSelectHandleLeftOffset : mSelectHandleRightOffset);
- }
- mSelectDraggingCursor.set(oldX, oldY);
- }
-
- private float distanceSquared(int x, int y, Point p) {
- float dx = p.x - x;
- float dy = p.y - y;
- return (dx * dx) + (dy * dy);
+ mSelectCursorBaseLayerId =
+ nativeGetHandleLayerId(mNativeClass, HANDLE_ID_BASE,
+ mSelectCursorBase, mSelectCursorBaseTextQuad);
+ mSelectCursorExtentLayerId =
+ nativeGetHandleLayerId(mNativeClass, HANDLE_ID_EXTENT,
+ mSelectCursorExtent, mSelectCursorExtentTextQuad);
}
private boolean setupWebkitSelect() {
@@ -5196,18 +5270,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
private void updateWebkitSelection() {
- int[] handles = null;
- if (mIsCaretSelection) {
- mSelectCursorRight.set(mSelectCursorLeft.x, mSelectCursorLeft.y);
- }
- if (mSelectingText) {
- handles = new int[4];
- getSelectionHandles(handles);
- } else {
- nativeSetTextSelection(mNativeClass, 0);
- }
+ int handleId = (mSelectDraggingCursor == mSelectCursorBase)
+ ? HANDLE_ID_BASE : HANDLE_ID_EXTENT;
mWebViewCore.removeMessages(EventHub.SELECT_TEXT);
- mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT, handles);
+ mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SELECT_TEXT,
+ mSelectDraggingCursor.x, mSelectDraggingCursor.y, (Integer)handleId);
}
private void resetCaretTimer() {
@@ -5219,14 +5286,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
/**
- * See {@link WebView#emulateShiftHeld()}
- */
- @Override
- @Deprecated
- public void emulateShiftHeld() {
- }
-
- /**
* Select all of the text in this WebView.
*
* This is an implementation detail.
@@ -5313,16 +5372,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
/**
- * This is an implementation detail.
- */
- public SearchBox getSearchBox() {
- if ((mWebViewCore == null) || (mWebViewCore.getBrowserFrame() == null)) {
- return null;
- }
- return mWebViewCore.getBrowserFrame().getSearchBox();
- }
-
- /**
* Returns the currently highlighted text as a string.
*/
String getSelection() {
@@ -5334,7 +5383,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
public void onAttachedToWindow() {
if (mWebView.hasWindowFocus()) setActive(true);
- if (isAccessibilityEnabled()) {
+ if (isAccessibilityInjectionEnabled()) {
getAccessibilityInjector().addAccessibilityApisIfNecessary();
}
@@ -5347,7 +5396,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mZoomManager.dismissZoomPicker();
if (mWebView.hasWindowFocus()) setActive(false);
- if (isAccessibilityEnabled()) {
+ if (isAccessibilityInjectionEnabled()) {
getAccessibilityInjector().removeAccessibilityApisIfNecessary();
} else {
// Ensure the injector is cleared if we're detaching from the window
@@ -5570,21 +5619,21 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
Point caretTop = calculateCaretTop();
if (visibleRect.width() < mEditTextContentBounds.width()) {
// The whole edit won't fit in the width, so use the caret rect
- if (mSelectCursorLeft.x < caretTop.x) {
- showRect.left = Math.max(0, mSelectCursorLeft.x - buffer);
+ if (mSelectCursorBase.x < caretTop.x) {
+ showRect.left = Math.max(0, mSelectCursorBase.x - buffer);
showRect.right = caretTop.x + buffer;
} else {
showRect.left = Math.max(0, caretTop.x - buffer);
- showRect.right = mSelectCursorLeft.x + buffer;
+ showRect.right = mSelectCursorBase.x + buffer;
}
}
if (visibleRect.height() < mEditTextContentBounds.height()) {
// The whole edit won't fit in the height, so use the caret rect
- if (mSelectCursorLeft.y > caretTop.y) {
+ if (mSelectCursorBase.y > caretTop.y) {
showRect.top = Math.max(0, caretTop.y - buffer);
- showRect.bottom = mSelectCursorLeft.y + buffer;
+ showRect.bottom = mSelectCursorBase.y + buffer;
} else {
- showRect.top = Math.max(0, mSelectCursorLeft.y - buffer);
+ showRect.top = Math.max(0, mSelectCursorBase.y - buffer);
showRect.bottom = caretTop.y + buffer;
}
}
@@ -5828,28 +5877,19 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
ensureSelectionHandles();
int shiftedY = y - getTitleHeight() + getScrollY();
int shiftedX = x + getScrollX();
- if (mSelectHandleCenter != null && mSelectHandleCenter.getBounds()
- .contains(shiftedX, shiftedY)) {
- mSelectionStarted = true;
- mSelectDraggingCursor = mSelectCursorLeft;
- mSelectDraggingOffset = mSelectHandleCenterOffset;
- mSelectDraggingTextQuad = mSelectCursorLeftTextQuad;
- mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
- hidePasteButton();
- } else if (mSelectHandleLeft != null
- && mSelectHandleLeft.getBounds()
- .contains(shiftedX, shiftedY)) {
+ if (mSelectHandleBaseBounds.contains(shiftedX, shiftedY)) {
mSelectionStarted = true;
- mSelectDraggingOffset = mSelectHandleLeftOffset;
- mSelectDraggingCursor = mSelectCursorLeft;
- mSelectDraggingTextQuad = mSelectCursorLeftTextQuad;
- } else if (mSelectHandleRight != null
- && mSelectHandleRight.getBounds()
+ mSelectDraggingCursor = mSelectCursorBase;
+ mSelectDraggingTextQuad = mSelectCursorBaseTextQuad;
+ if (mIsCaretSelection) {
+ mPrivateHandler.removeMessages(CLEAR_CARET_HANDLE);
+ hidePasteButton();
+ }
+ } else if (mSelectHandleExtentBounds
.contains(shiftedX, shiftedY)) {
mSelectionStarted = true;
- mSelectDraggingOffset = mSelectHandleRightOffset;
- mSelectDraggingCursor = mSelectCursorRight;
- mSelectDraggingTextQuad = mSelectCursorRightTextQuad;
+ mSelectDraggingCursor = mSelectCursorExtent;
+ mSelectDraggingTextQuad = mSelectCursorExtentTextQuad;
} else if (mIsCaretSelection) {
selectionDone();
}
@@ -5894,9 +5934,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
if (deltaX != 0 || deltaY != 0) {
int handleX = contentX +
- viewToContentDimension(mSelectDraggingOffset.x);
+ viewToContentDimension(mSelectOffset.x);
int handleY = contentY +
- viewToContentDimension(mSelectDraggingOffset.y);
+ viewToContentDimension(mSelectOffset.y);
mSelectDraggingCursor.set(handleX, handleY);
boolean inCursorText =
mSelectDraggingTextQuad.containsPoint(handleX, handleY);
@@ -6033,12 +6073,15 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
}
case MotionEvent.ACTION_UP: {
- endScrollEdit();
- if (!mConfirmMove && mIsEditingText && mSelectionStarted &&
- mIsCaretSelection) {
- showPasteWindow();
- stopTouch();
- break;
+ if (mIsEditingText && mSelectionStarted) {
+ endScrollEdit();
+ mPrivateHandler.sendEmptyMessageDelayed(SCROLL_HANDLE_INTO_VIEW,
+ TEXT_SCROLL_FIRST_SCROLL_MS);
+ if (!mConfirmMove && mIsCaretSelection) {
+ showPasteWindow();
+ stopTouch();
+ break;
+ }
}
mLastTouchUpTime = eventTime;
if (mSentAutoScrollMessage) {
@@ -6145,6 +6188,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
}
+ private static int getSelectionCoordinate(int coordinate, int min, int max) {
+ return Math.max(Math.min(coordinate, max), min);
+ }
+
private void beginScrollEdit() {
if (mLastEditScroll == 0) {
mLastEditScroll = SystemClock.uptimeMillis() -
@@ -6153,10 +6200,37 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
}
+ private void scrollDraggedSelectionHandleIntoView() {
+ if (mSelectDraggingCursor == null) {
+ return;
+ }
+ int x = mSelectDraggingCursor.x;
+ int y = mSelectDraggingCursor.y;
+ if (!mEditTextContentBounds.contains(x,y)) {
+ int left = Math.min(0, x - mEditTextContentBounds.left - EDIT_RECT_BUFFER);
+ int right = Math.max(0, x - mEditTextContentBounds.right + EDIT_RECT_BUFFER);
+ int deltaX = left + right;
+ int above = Math.min(0, y - mEditTextContentBounds.top - EDIT_RECT_BUFFER);
+ int below = Math.max(0, y - mEditTextContentBounds.bottom + EDIT_RECT_BUFFER);
+ int deltaY = above + below;
+ if (deltaX != 0 || deltaY != 0) {
+ int scrollX = getTextScrollX() + deltaX;
+ int scrollY = getTextScrollY() + deltaY;
+ scrollX = clampBetween(scrollX, 0, getMaxTextScrollX());
+ scrollY = clampBetween(scrollY, 0, getMaxTextScrollY());
+ scrollEditText(scrollX, scrollY);
+ }
+ }
+ }
+
private void endScrollEdit() {
mLastEditScroll = 0;
}
+ private static int clampBetween(int value, int min, int max) {
+ return Math.max(min, Math.min(value, max));
+ }
+
private static int getTextScrollDelta(float speed, long deltaT) {
float distance = speed * deltaT;
int intDistance = (int)Math.floor(distance);
@@ -6172,10 +6246,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
*/
private void scrollEditWithCursor() {
if (mLastEditScroll != 0) {
- int x = viewToContentX(mLastTouchX + getScrollX() + mSelectDraggingOffset.x);
+ int x = viewToContentX(mLastTouchX + getScrollX() + mSelectOffset.x);
float scrollSpeedX = getTextScrollSpeed(x, mEditTextContentBounds.left,
mEditTextContentBounds.right);
- int y = viewToContentY(mLastTouchY + getScrollY() + mSelectDraggingOffset.y);
+ int y = viewToContentY(mLastTouchY + getScrollY() + mSelectOffset.y);
float scrollSpeedY = getTextScrollSpeed(y, mEditTextContentBounds.top,
mEditTextContentBounds.bottom);
if (scrollSpeedX == 0.0f && scrollSpeedY == 0.0f) {
@@ -6185,24 +6259,27 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
long timeSinceLastUpdate = currentTime - mLastEditScroll;
int deltaX = getTextScrollDelta(scrollSpeedX, timeSinceLastUpdate);
int deltaY = getTextScrollDelta(scrollSpeedY, timeSinceLastUpdate);
+ int scrollX = getTextScrollX() + deltaX;
+ scrollX = clampBetween(scrollX, 0, getMaxTextScrollX());
+ int scrollY = getTextScrollY() + deltaY;
+ scrollY = clampBetween(scrollY, 0, getMaxTextScrollY());
+
mLastEditScroll = currentTime;
- if (deltaX == 0 && deltaY == 0) {
+ if (scrollX == getTextScrollX() && scrollY == getTextScrollY()) {
// By probability no text scroll this time. Try again later.
mPrivateHandler.sendEmptyMessageDelayed(SCROLL_EDIT_TEXT,
TEXT_SCROLL_FIRST_SCROLL_MS);
} else {
- int scrollX = getTextScrollX() + deltaX;
- scrollX = Math.min(getMaxTextScrollX(), scrollX);
- scrollX = Math.max(0, scrollX);
- int scrollY = getTextScrollY() + deltaY;
- scrollY = Math.min(getMaxTextScrollY(), scrollY);
- scrollY = Math.max(0, scrollY);
- scrollEditText(scrollX, scrollY);
- int cursorX = mSelectDraggingCursor.x;
- int cursorY = mSelectDraggingCursor.y;
- mSelectDraggingCursor.set(x - deltaX, y - deltaY);
+ int selectionX = getSelectionCoordinate(x,
+ mEditTextContentBounds.left, mEditTextContentBounds.right);
+ int selectionY = getSelectionCoordinate(y,
+ mEditTextContentBounds.top, mEditTextContentBounds.bottom);
+ int oldX = mSelectDraggingCursor.x;
+ int oldY = mSelectDraggingCursor.y;
+ mSelectDraggingCursor.set(selectionX, selectionY);
updateWebkitSelection();
- mSelectDraggingCursor.set(cursorX, cursorY);
+ scrollEditText(scrollX, scrollY);
+ mSelectDraggingCursor.set(oldX, oldY);
}
}
}
@@ -6258,10 +6335,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// scrolling. The rectangle is in document coordinates.
final int maxX = mScrollingLayerRect.right;
final int maxY = mScrollingLayerRect.bottom;
- final int resultX = Math.max(0,
- Math.min(mScrollingLayerRect.left + contentX, maxX));
- final int resultY = Math.max(0,
- Math.min(mScrollingLayerRect.top + contentY, maxY));
+ final int resultX = clampBetween(maxX, 0,
+ mScrollingLayerRect.left + contentX);
+ final int resultY = clampBetween(maxY, 0,
+ mScrollingLayerRect.top + contentY);
if (resultX != mScrollingLayerRect.left
|| resultY != mScrollingLayerRect.top
@@ -6362,10 +6439,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
int x = Math.round(newX);
int y = Math.round(newY);
if (mIsEditingText) {
- x = Math.max(mEditTextContentBounds.left,
- Math.min(mEditTextContentBounds.right, x));
- y = Math.max(mEditTextContentBounds.top,
- Math.min(mEditTextContentBounds.bottom, y));
+ x = clampBetween(x, mEditTextContentBounds.left,
+ mEditTextContentBounds.right);
+ y = clampBetween(y, mEditTextContentBounds.top,
+ mEditTextContentBounds.bottom);
}
mSelectDraggingCursor.set(x, y);
}
@@ -6394,9 +6471,13 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mWebViewPrivate.getVerticalScrollFactor());
final int hdelta = (int) (hscroll *
mWebViewPrivate.getHorizontalScrollFactor());
- if (pinScrollBy(hdelta, vdelta, false, 0)) {
- return true;
- }
+
+ abortAnimation();
+ int oldTouchMode = mTouchMode;
+ startScrollingLayer(event.getX(), event.getY());
+ doDrag(hdelta, vdelta);
+ mTouchMode = oldTouchMode;
+ return true;
}
}
}
@@ -6428,9 +6509,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private long mTrackballUpTime = 0;
private long mLastCursorTime = 0;
private Rect mLastCursorBounds;
- private SelectionHandleAlpha mHandleAlpha = new SelectionHandleAlpha();
- private ObjectAnimator mHandleAlphaAnimator =
- ObjectAnimator.ofInt(mHandleAlpha, "alpha", 0);
+ private SelectionHandleAlpha mBaseAlpha = new SelectionHandleAlpha();
+ private SelectionHandleAlpha mExtentAlpha = new SelectionHandleAlpha();
+ private ObjectAnimator mBaseHandleAlphaAnimator =
+ ObjectAnimator.ofInt(mBaseAlpha, "alpha", 0);
+ private ObjectAnimator mExtentHandleAlphaAnimator =
+ ObjectAnimator.ofInt(mExtentAlpha, "alpha", 0);
// Set by default; BrowserActivity clears to interpret trackball data
// directly for movement. Currently, the framework only passes
@@ -6440,6 +6524,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private DrawData mDelaySetPicture;
private DrawData mLoadedPicture;
+ @Override
public void setMapTrackballToArrowKeys(boolean setMap) {
mMapTrackballToArrowKeys = setMap;
}
@@ -6666,6 +6751,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
}
+ @Override
public void flingScroll(int vx, int vy) {
mScroller.fling(getScrollX(), getScrollY(), vx, vy, 0, computeMaxScrollX(), 0,
computeMaxScrollY(), mOverflingDistance, mOverflingDistance);
@@ -6918,6 +7004,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+ // Check if we are destroyed
+ if (mWebViewCore == null) return false;
// FIXME: If a subwindow is showing find, and the user touches the
// background window, it can steal focus.
if (mFindIsUp) return false;
@@ -7227,11 +7315,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// nativeCreate sets mNativeClass to a non-zero value
String drawableDir = BrowserFrame.getRawResFilename(
BrowserFrame.DRAWABLEDIR, mContext);
- WindowManager windowManager =
- (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- Display display = windowManager.getDefaultDisplay();
- nativeCreate(msg.arg1, drawableDir,
- ActivityManager.isHighEndGfx(display));
+ nativeCreate(msg.arg1, drawableDir, ActivityManager.isHighEndGfx());
if (mDelaySetPicture != null) {
setNewPicture(mDelaySetPicture, true);
mDelaySetPicture = null;
@@ -7317,15 +7401,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mWebView.setKeepScreenOn(msg.arg1 == 1);
break;
- case ENTER_FULLSCREEN_VIDEO:
- int layerId = msg.arg1;
-
- String url = (String) msg.obj;
- if (mHTML5VideoViewProxy != null) {
- mHTML5VideoViewProxy.enterFullScreenVideo(layerId, url);
- }
- break;
-
case EXIT_FULLSCREEN_VIDEO:
if (mHTML5VideoViewProxy != null) {
mHTML5VideoViewProxy.exitFullScreenVideo();
@@ -7405,7 +7480,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
case SELECTION_STRING_CHANGED:
- if (isAccessibilityEnabled()) {
+ if (isAccessibilityInjectionEnabled()) {
getAccessibilityInjector()
.handleSelectionChangedIfNecessary((String) msg.obj);
}
@@ -7464,7 +7539,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mEditTextLayerId = initData.mNodeLayerId;
nativeMapLayerRect(mNativeClass, mEditTextLayerId,
mEditTextContentBounds);
- mEditTextContent.set(initData.mContentRect);
+ mEditTextContent.set(initData.mClientRect);
relocateAutoCompletePopup();
}
break;
@@ -7545,6 +7620,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
scrollEditWithCursor();
break;
+ case SCROLL_HANDLE_INTO_VIEW:
+ scrollDraggedSelectionHandleIntoView();
+ break;
+
default:
super.handleMessage(msg);
break;
@@ -7580,8 +7659,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
.contains(x, y);
} else {
isPressingHandle =
- mSelectHandleLeft.getBounds().contains(x, y)
- || mSelectHandleRight.getBounds().contains(x, y);
+ mSelectHandleBaseBounds.contains(x, y)
+ || mSelectHandleExtentBounds.contains(x, y);
}
return isPressingHandle;
}
@@ -7869,7 +7948,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
int functor = 0;
boolean forceInval = isPictureAfterFirstLayout;
ViewRootImpl viewRoot = mWebView.getViewRootImpl();
- if (mWebView.isHardwareAccelerated() && viewRoot != null) {
+ if (mWebView.isHardwareAccelerated()
+ && mWebView.getLayerType() != View.LAYER_TYPE_SOFTWARE
+ && viewRoot != null) {
functor = nativeGetDrawGLFunction(mNativeClass);
if (functor != 0) {
// force an invalidate if functor attach not successful
@@ -7928,8 +8009,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (data.mSelectTextPtr != 0 &&
(data.mStart != data.mEnd ||
- (mFieldPointer == nodePointer && mFieldPointer != 0))) {
- mIsCaretSelection = (data.mStart == data.mEnd);
+ (mFieldPointer == nodePointer && mFieldPointer != 0) ||
+ (nodePointer == 0 && data.mStart == 0 && data.mEnd == 0))) {
+ mIsEditingText = (mFieldPointer == nodePointer) && nodePointer != 0;
+ mIsCaretSelection = (data.mStart == data.mEnd && nodePointer != 0);
if (mIsCaretSelection &&
(mInputConnection == null ||
mInputConnection.getEditable().length() == 0)) {
@@ -7938,11 +8021,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
} else {
if (!mSelectingText) {
setupWebkitSelect();
- } else if (!mSelectionStarted) {
- syncSelectionCursors();
} else {
- adjustSelectionCursors();
+ syncSelectionCursors();
}
+ animateHandles();
if (mIsCaretSelection) {
resetCaretTimer();
}
@@ -7958,8 +8040,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
float maxScrollX = getMaxTextScrollX();
float scrollPercentX = ((float)scrollX)/maxScrollX;
mEditTextContent.offsetTo(-scrollX, -scrollY);
- mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SCROLL_TEXT_INPUT, 0,
+ mWebViewCore.removeMessages(EventHub.SCROLL_TEXT_INPUT);
+ mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, 0,
scrollY, (Float)scrollPercentX);
+ animateHandles();
}
private void beginTextBatch() {
@@ -8404,14 +8488,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
/**
- * See {@link WebView#debugDump()}
- */
- @Override
- @Deprecated
- public void debugDump() {
- }
-
- /**
* Enable the communication b/t the webView and VideoViewProxy
*
* only used by the Browser
@@ -8545,6 +8621,54 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
WebViewCore.setShouldMonitorWebCoreThread();
}
+ @Override
+ public void dumpViewHierarchyWithProperties(BufferedWriter out, int level) {
+ int layer = getBaseLayer();
+ if (layer != 0) {
+ try {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ ViewStateSerializer.dumpLayerHierarchy(layer, stream, level);
+ stream.close();
+ byte[] buf = stream.toByteArray();
+ out.write(new String(buf, "ascii"));
+ } catch (IOException e) {}
+ }
+ }
+
+ @Override
+ public View findHierarchyView(String className, int hashCode) {
+ if (mNativeClass == 0) return null;
+ Picture pic = new Picture();
+ if (!nativeDumpLayerContentToPicture(mNativeClass, className, hashCode, pic)) {
+ return null;
+ }
+ return new PictureWrapperView(getContext(), pic, mWebView);
+ }
+
+ private static class PictureWrapperView extends View {
+ Picture mPicture;
+ WebView mWebView;
+
+ public PictureWrapperView(Context context, Picture picture, WebView parent) {
+ super(context);
+ mPicture = picture;
+ mWebView = parent;
+ setWillNotDraw(false);
+ setRight(mPicture.getWidth());
+ setBottom(mPicture.getHeight());
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ canvas.drawPicture(mPicture);
+ }
+
+ @Override
+ public boolean post(Runnable action) {
+ return mWebView.post(action);
+ }
+ }
+
private native void nativeCreate(int ptr, String drawableDir, boolean isHighEndGfx);
private native void nativeDebugDump();
private static native void nativeDestroy(int ptr);
@@ -8565,6 +8689,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
int scrollingLayer);
private native int nativeGetBaseLayer(int nativeInstance);
private native void nativeCopyBaseContentToPicture(Picture pict);
+ private native boolean nativeDumpLayerContentToPicture(int nativeInstance,
+ String className, int layerId, Picture pict);
private native boolean nativeHasContent();
private native void nativeStopGL(int ptr);
private native void nativeDiscardAllTextures();
@@ -8608,4 +8734,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private static native int nativeSetHwAccelerated(int instance, boolean hwAccelerated);
private static native void nativeFindMaxVisibleRect(int instance, int layerId,
Rect visibleContentRect);
+ private static native boolean nativeIsHandleLeft(int instance, int handleId);
+ private static native boolean nativeIsPointVisible(int instance,
+ int layerId, int contentX, int contentY);
}
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 6aff10a..08a046a 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -204,43 +204,16 @@ public class WebViewClient {
}
/**
- * Notify the host application that an SSL error occurred while loading a
- * resource, but the WebView chose to proceed anyway based on a
- * decision retained from a previous response to onReceivedSslError().
- * @hide
- */
- public void onProceededAfterSslError(WebView view, SslError error) {
- }
-
- /**
- * Notify the host application to handle a SSL client certificate
- * request (display the request to the user and ask whether to
- * proceed with a client certificate or not). The host application
- * has to call either handler.cancel() or handler.proceed() as the
- * connection is suspended and waiting for the response. The
- * default behavior is to cancel, returning no client certificate.
- *
- * @param view The WebView that is initiating the callback.
- * @param handler A ClientCertRequestHandler object that will
- * handle the user's response.
- * @param host_and_port The host and port of the requesting server.
+ * Notifies the host application that the WebView received an HTTP
+ * authentication request. The host application can use the supplied
+ * {@link HttpAuthHandler} to set the WebView's response to the request.
+ * The default behavior is to cancel the request.
*
- * @hide
- */
- public void onReceivedClientCertRequest(WebView view,
- ClientCertRequestHandler handler, String host_and_port) {
- handler.cancel();
- }
-
- /**
- * Notify the host application to handle an authentication request. The
- * default behavior is to cancel the request.
- *
- * @param view The WebView that is initiating the callback.
- * @param handler The HttpAuthHandler that will handle the user's response.
- * @param host The host requiring authentication.
- * @param realm A description to help store user credentials for future
- * visits.
+ * @param view the WebView that is initiating the callback
+ * @param handler the HttpAuthHandler used to set the WebView's response
+ * @param host the host requiring authentication
+ * @param realm the realm for which authentication is required
+ * @see Webview#getHttpAuthUsernamePassword
*/
public void onReceivedHttpAuthRequest(WebView view,
HttpAuthHandler handler, String host, String realm) {
diff --git a/core/java/android/webkit/WebViewClientClassicExt.java b/core/java/android/webkit/WebViewClientClassicExt.java
new file mode 100644
index 0000000..a873585
--- /dev/null
+++ b/core/java/android/webkit/WebViewClientClassicExt.java
@@ -0,0 +1,53 @@
+/*
+ * 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.net.http.SslError;
+
+/**
+ * Adds WebViewClassic specific extension methods to the WebViewClient callback class.
+ * These are not part of the public WebView API, so the class is hidden.
+ * @hide
+ */
+public class WebViewClientClassicExt extends WebViewClient {
+
+ /**
+ * Notify the host application that an SSL error occurred while loading a
+ * resource, but the WebView chose to proceed anyway based on a
+ * decision retained from a previous response to onReceivedSslError().
+ */
+ public void onProceededAfterSslError(WebView view, SslError error) {
+ }
+
+ /**
+ * Notify the host application to handle a SSL client certificate
+ * request (display the request to the user and ask whether to
+ * proceed with a client certificate or not). The host application
+ * has to call either handler.cancel() or handler.proceed() as the
+ * connection is suspended and waiting for the response. The
+ * default behavior is to cancel, returning no client certificate.
+ *
+ * @param view The WebView that is initiating the callback.
+ * @param handler A ClientCertRequestHandler object that will
+ * handle the user's response.
+ * @param host_and_port The host and port of the requesting server.
+ */
+ public void onReceivedClientCertRequest(WebView view,
+ ClientCertRequestHandler handler, String host_and_port) {
+ handler.cancel();
+ }
+}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 728ddbf..3fb3ec6 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -132,6 +132,8 @@ public final class WebViewCore {
private int mRestoredX = 0;
private int mRestoredY = 0;
+ private MockGeolocation mMockGeolocation = new MockGeolocation(this);
+
private DeviceMotionAndOrientationManager mDeviceMotionAndOrientationManager =
new DeviceMotionAndOrientationManager(this);
private DeviceMotionService mDeviceMotionService;
@@ -441,7 +443,7 @@ public final class WebViewCore {
}
/**
- * Notify the browser that the origin has exceeded it's database quota.
+ * Notify the embedding application that the origin has exceeded it's database quota.
* @param url The URL that caused the overflow.
* @param databaseIdentifier The identifier of the database.
* @param quota The current quota for the origin.
@@ -466,12 +468,15 @@ public final class WebViewCore {
}
/**
- * Notify the browser that the appcache has exceeded its max size.
+ * Notify the embedding application that the appcache has reached or exceeded its maximum
+ * allowed storage size.
+ *
* @param requiredStorage is the amount of storage, in bytes, that would be
* needed in order for the last appcache operation to succeed.
+ * @param maxSize maximum allowed Application Cache database size, in bytes.
*/
- protected void reachedMaxAppCacheSize(long requiredStorage) {
- mCallbackProxy.onReachedMaxAppCacheSize(requiredStorage, getUsedQuota(),
+ protected void reachedMaxAppCacheSize(long requiredStorage, long maxSize) {
+ mCallbackProxy.onReachedMaxAppCacheSize(requiredStorage, maxSize,
new WebStorage.QuotaUpdater() {
@Override
public void updateQuota(long newQuota) {
@@ -560,24 +565,6 @@ public final class WebViewCore {
}
/**
- * Notify the webview that this is an installable web app.
- */
- protected void setInstallableWebApp() {
- mCallbackProxy.setInstallableWebApp();
- }
-
- /**
- * Notify the webview that we want to display the video layer fullscreen.
- */
- protected void enterFullscreenForVideoLayer(int layerId, String url) {
- if (mWebViewClassic == null) return;
- Message message = Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.ENTER_FULLSCREEN_VIDEO, layerId, 0);
- message.obj = url;
- message.sendToTarget();
- }
-
- /**
* Notify the webview that we want to exit the video fullscreen.
* This is called through JNI by webcore.
*/
@@ -619,8 +606,6 @@ public final class WebViewCore {
*/
private native void nativeNotifyAnimationStarted(int nativeClass);
- private native boolean nativeFocusBoundsChanged(int nativeClass);
-
private native boolean nativeKey(int nativeClass, int keyCode,
int unichar, int repeatCount, boolean isShift, boolean isAlt,
boolean isSym, boolean isDown);
@@ -839,6 +824,7 @@ public final class WebViewCore {
static class JSInterfaceData {
Object mObject;
String mInterfaceName;
+ boolean mRequireAnnotation;
}
static class JSKeyData {
@@ -960,7 +946,7 @@ public final class WebViewCore {
public int mMaxLength;
public Rect mContentBounds;
public int mNodeLayerId;
- public Rect mContentRect;
+ public Rect mClientRect;
}
// mAction of TouchEventData can be MotionEvent.getAction() which uses the
@@ -1199,6 +1185,7 @@ public final class WebViewCore {
static final int SET_INITIAL_FOCUS = 224;
static final int SAVE_VIEW_STATE = 225;
+ static final int SET_USE_MOCK_GEOLOCATION = 226;
// Private handler for WebCore messages.
private Handler mHandler;
@@ -1306,13 +1293,8 @@ public final class WebViewCore {
} else {
xPercent = ((Float) msg.obj).floatValue();
}
- Rect contentBounds = new Rect();
nativeScrollFocusedTextInput(mNativeClass, xPercent,
- msg.arg2, contentBounds);
- Message.obtain(
- mWebViewClassic.mPrivateHandler,
- WebViewClassic.UPDATE_CONTENT_BOUNDS,
- contentBounds).sendToTarget();
+ msg.arg2);
break;
case LOAD_URL: {
@@ -1508,7 +1490,7 @@ public final class WebViewCore {
case ADD_JS_INTERFACE:
JSInterfaceData jsData = (JSInterfaceData) msg.obj;
mBrowserFrame.addJavascriptInterface(jsData.mObject,
- jsData.mInterfaceName);
+ jsData.mInterfaceName, jsData.mRequireAnnotation);
break;
case REMOVE_JS_INTERFACE:
@@ -1660,6 +1642,10 @@ public final class WebViewCore {
(Set<String>) msg.obj);
break;
+ case SET_USE_MOCK_GEOLOCATION:
+ setUseMockGeolocation();
+ break;
+
case SET_USE_MOCK_DEVICE_ORIENTATION:
setUseMockDeviceOrientation();
break;
@@ -1708,13 +1694,9 @@ public final class WebViewCore {
nativeInsertText(mNativeClass, (String) msg.obj);
break;
case SELECT_TEXT: {
- int[] args = (int[]) msg.obj;
- if (args == null) {
- nativeClearTextSelection(mNativeClass);
- } else {
- nativeSelectText(mNativeClass, args[0],
- args[1], args[2], args[3]);
- }
+ int handleId = (Integer) msg.obj;
+ nativeSelectText(mNativeClass, handleId,
+ msg.arg1, msg.arg2);
break;
}
case SELECT_WORD_AT: {
@@ -2139,8 +2121,8 @@ public final class WebViewCore {
return width;
}
- // Utility method for exceededDatabaseQuota and reachedMaxAppCacheSize
- // callbacks. Computes the sum of database quota for all origins.
+ // Utility method for exceededDatabaseQuota callback. Computes the sum
+ // of WebSQL database quota for all origins.
private long getUsedQuota() {
WebStorageClassic webStorage = WebStorageClassic.getInstance();
Collection<WebStorage.Origin> origins = webStorage.getOriginsSync();
@@ -2192,7 +2174,6 @@ public final class WebViewCore {
// only non-null if it is for the first picture set after the first layout
ViewState mViewState;
boolean mFirstLayoutForNonStandardLoad;
- boolean mFocusSizeChanged;
}
DrawData mLastDrawData = null;
@@ -2247,7 +2228,6 @@ public final class WebViewCore {
private void webkitDraw(DrawData draw) {
if (mWebViewClassic != null) {
- draw.mFocusSizeChanged = nativeFocusBoundsChanged(mNativeClass);
draw.mViewSize = new Point(mCurrentViewWidth, mCurrentViewHeight);
if (mSettings.getUseWideViewPort()) {
draw.mMinPrefWidth = Math.max(
@@ -2330,7 +2310,6 @@ public final class WebViewCore {
Log.w(LOGTAG, "Cannot pauseUpdatePicture, core destroyed or not initialized!");
return;
}
- core.nativeSetIsPaused(core.mNativeClass, true);
core.mDrawIsPaused = true;
}
}
@@ -2348,7 +2327,6 @@ public final class WebViewCore {
Log.w(LOGTAG, "Cannot resumeUpdatePicture, core destroyed!");
return;
}
- core.nativeSetIsPaused(core.mNativeClass, false);
core.mDrawIsPaused = false;
// always redraw on resume to reenable gif animations
core.mDrawIsScheduled = false;
@@ -2363,13 +2341,13 @@ public final class WebViewCore {
//////////////////////////////////////////////////////////////////////////
private void restoreState(int index) {
- WebBackForwardList list = mCallbackProxy.getBackForwardList();
+ WebBackForwardListClassic list = mCallbackProxy.getBackForwardList();
int size = list.getSize();
for (int i = 0; i < size; i++) {
list.getItemAtIndex(i).inflate(mBrowserFrame.mNativeFrame);
}
mBrowserFrame.mLoadInitFromJava = true;
- list.restoreIndex(mBrowserFrame.mNativeFrame, index);
+ WebBackForwardListClassic.restoreIndex(mBrowserFrame.mNativeFrame, index);
mBrowserFrame.mLoadInitFromJava = false;
}
@@ -2501,6 +2479,13 @@ public final class WebViewCore {
setupViewport(true);
}
+ static float getFixedDisplayDensity(Context context) {
+ // We make bad assumptions about multiplying and dividing density by 100,
+ // force them to be true with this hack
+ float density = context.getResources().getDisplayMetrics().density;
+ return ((int) (density * 100)) / 100.0f;
+ }
+
private void setupViewport(boolean updateViewState) {
if (mWebViewClassic == null || mSettings == null) {
// We've been destroyed or are being destroyed, return early
@@ -2545,11 +2530,13 @@ public final class WebViewCore {
// adjust the default scale to match the densityDpi
float adjust = 1.0f;
if (mViewportDensityDpi == -1) {
- adjust = mContext.getResources().getDisplayMetrics().density;
+ adjust = getFixedDisplayDensity(mContext);
} else if (mViewportDensityDpi > 0) {
adjust = (float) mContext.getResources().getDisplayMetrics().densityDpi
/ mViewportDensityDpi;
+ adjust = ((int) (adjust * 100)) / 100.0f;
}
+
// Remove any update density messages in flight.
// If the density is indeed different from WebView's default scale,
// a new message will be queued.
@@ -2787,14 +2774,11 @@ public final class WebViewCore {
}
// called by JNI
- private void updateTextfield(int ptr, boolean changeToPassword,
- String text, int textGeneration) {
+ private void updateTextfield(int ptr, String text, int textGeneration) {
if (mWebViewClassic != null) {
- Message msg = Message.obtain(mWebViewClassic.mPrivateHandler,
+ Message.obtain(mWebViewClassic.mPrivateHandler,
WebViewClassic.UPDATE_TEXTFIELD_TEXT_MSG_ID, ptr,
- textGeneration, text);
- msg.getData().putBoolean("password", changeToPassword);
- msg.sendToTarget();
+ textGeneration, text).sendToTarget();
}
}
@@ -2855,7 +2839,7 @@ public final class WebViewCore {
* Scroll the focused textfield to (xPercent, y) in document space
*/
private native void nativeScrollFocusedTextInput(int nativeClass,
- float xPercent, int y, Rect contentBounds);
+ float xPercent, int y);
// these must be in document space (i.e. not scaled/zoomed).
private native void nativeSetScrollOffset(int nativeClass,
@@ -3063,6 +3047,22 @@ public final class WebViewCore {
mDeviceMotionAndOrientationManager.setUseMock();
}
+ private void setUseMockGeolocation() {
+ mMockGeolocation.setUseMock();
+ }
+
+ public void setMockGeolocationPosition(double latitude, double longitude, double accuracy) {
+ mMockGeolocation.setPosition(latitude, longitude, accuracy);
+ }
+
+ public void setMockGeolocationError(int code, String message) {
+ mMockGeolocation.setError(code, message);
+ }
+
+ public void setMockGeolocationPermission(boolean allow) {
+ mMockGeolocation.setPermission(allow);
+ }
+
public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
mDeviceMotionAndOrientationManager.setMockOrientation(canProvideAlpha, alpha,
@@ -3089,7 +3089,6 @@ public final class WebViewCore {
sShouldMonitorWebCoreThread = true;
}
- private native void nativeSetIsPaused(int nativeClass, boolean isPaused);
private native void nativePause(int nativeClass);
private native void nativeResume(int nativeClass);
private native void nativeFreeMemory(int nativeClass);
@@ -3135,7 +3134,7 @@ public final class WebViewCore {
private native String nativeGetText(int nativeClass,
int startX, int startY, int endX, int endY);
private native void nativeSelectText(int nativeClass,
- int startX, int startY, int endX, int endY);
+ int handleId, int x, int y);
private native void nativeClearTextSelection(int nativeClass);
private native boolean nativeSelectWordAt(int nativeClass, int x, int y);
private native void nativeSelectAll(int nativeClass);
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index 9d10d67..5597259 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -23,13 +23,15 @@ import android.content.Context;
* application has stored any of the following types of browsing data and
* to clear any such stored data for all WebViews in the application.
* <ul>
- * <li>Username/password pairs entered into web forms</li>
+ * <li>Username/password pairs for web forms</li>
* <li>HTTP authentication username/password pairs</li>
* <li>Data entered into text fields (e.g. for autocomplete suggestions)</li>
* </ul>
*/
public class WebViewDatabase {
- // TODO: deprecate/hide this.
+ /**
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+ */
protected static final String LOGTAG = "webviewdatabase";
/**
@@ -38,55 +40,70 @@ public class WebViewDatabase {
protected WebViewDatabase() {
}
- public static synchronized WebViewDatabase getInstance(Context context) {
+ public static WebViewDatabase getInstance(Context context) {
return WebViewFactory.getProvider().getWebViewDatabase(context);
}
/**
- * Gets whether there are any username/password combinations
- * from web pages saved.
+ * Gets whether there are any saved username/password pairs for web forms.
+ * Note that these are unrelated to HTTP authentication credentials.
*
- * @return true if there are any username/passwords used in web
- * forms saved
+ * @return true if there are any saved username/password pairs
+ * @see WebView#savePassword
+ * @see clearUsernamePassword
*/
public boolean hasUsernamePassword() {
throw new MustOverrideException();
}
/**
- * Clears any username/password combinations saved from web forms.
+ * Clears any saved username/password pairs for web forms.
+ * Note that these are unrelated to HTTP authentication credentials.
+ *
+ * @see WebView#savePassword
+ * @see hasUsernamePassword
*/
public void clearUsernamePassword() {
throw new MustOverrideException();
}
/**
- * Gets whether there are any HTTP authentication username/password combinations saved.
+ * Gets whether there are any saved credentials for HTTP authentication.
*
- * @return true if there are any HTTP authentication username/passwords saved
+ * @return whether there are any saved credentials
+ * @see Webview#getHttpAuthUsernamePassword
+ * @see Webview#setHttpAuthUsernamePassword
+ * @see clearHttpAuthUsernamePassword
*/
public boolean hasHttpAuthUsernamePassword() {
throw new MustOverrideException();
}
/**
- * Clears any HTTP authentication username/passwords that are saved.
+ * Clears any saved credentials for HTTP authentication.
+ *
+ * @see Webview#getHttpAuthUsernamePassword
+ * @see Webview#setHttpAuthUsernamePassword
+ * @see hasHttpAuthUsernamePassword
*/
public void clearHttpAuthUsernamePassword() {
throw new MustOverrideException();
}
/**
- * Gets whether there is any previously-entered form data saved.
+ * Gets whether there is any saved data for web forms.
*
- * @return true if there is form data saved
+ * @return whether there is any saved data for web forms
+ * @see clearFormData
*/
public boolean hasFormData() {
throw new MustOverrideException();
}
/**
- * Clears any stored previously-entered form data.
+ * Clears any saved data for web forms.
+ *
+ * @see hasFormData
*/
public void clearFormData() {
throw new MustOverrideException();
diff --git a/core/java/android/webkit/WebViewDatabaseClassic.java b/core/java/android/webkit/WebViewDatabaseClassic.java
index 9b1d4cb..be01028 100644
--- a/core/java/android/webkit/WebViewDatabaseClassic.java
+++ b/core/java/android/webkit/WebViewDatabaseClassic.java
@@ -52,6 +52,7 @@ final class WebViewDatabaseClassic extends WebViewDatabase {
// implemented for b/5265606.
private static WebViewDatabaseClassic sInstance = null;
+ private static final Object sInstanceLock = new Object();
private static SQLiteDatabase sDatabase = null;
@@ -99,7 +100,8 @@ final class WebViewDatabaseClassic extends WebViewDatabase {
// Initially true until the background thread completes.
private boolean mInitialized = false;
- WebViewDatabaseClassic(final Context context) {
+ private WebViewDatabaseClassic(final Context context) {
+ JniUtil.setContext(context);
new Thread() {
@Override
public void run() {
@@ -110,11 +112,13 @@ final class WebViewDatabaseClassic extends WebViewDatabase {
// Singleton only, use getInstance()
}
- public static synchronized WebViewDatabaseClassic getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new WebViewDatabaseClassic(context);
+ public static WebViewDatabaseClassic getInstance(Context context) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ sInstance = new WebViewDatabaseClassic(context);
+ }
+ return sInstance;
}
- return sInstance;
}
private synchronized void init(Context context) {
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 73ae910..b833a01 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -16,14 +16,23 @@
package android.webkit;
+import android.os.Build;
+import android.os.StrictMode;
+import android.os.SystemProperties;
import android.util.Log;
+import dalvik.system.PathClassLoader;
+
/**
* Top level factory, used creating all the main WebView implementation classes.
*/
class WebViewFactory {
// Default Provider factory class name.
- private static final String DEFAULT_WEB_VIEW_FACTORY = "android.webkit.WebViewClassic$Factory";
+ // TODO: When the Chromium powered WebView is ready, it should be the default factory class.
+ private static final String DEFAULT_WEBVIEW_FACTORY = "android.webkit.WebViewClassic$Factory";
+ private static final String CHROMIUM_WEBVIEW_FACTORY =
+ "com.android.webviewchromium.WebViewChromiumFactoryProvider";
+ private static final String CHROMIUM_WEBVIEW_JAR = "/system/framework/webviewchromium.jar";
private static final String LOGTAG = "WebViewFactory";
@@ -32,24 +41,54 @@ class WebViewFactory {
// Cache the factory both for efficiency, and ensure any one process gets all webviews from the
// same provider.
private static WebViewFactoryProvider sProviderInstance;
+ private static final Object sProviderLock = new Object();
+
+ static WebViewFactoryProvider getProvider() {
+ synchronized (sProviderLock) {
+ // For now the main purpose of this function (and the factory abstraction) is to keep
+ // us honest and minimize usage of WebViewClassic internals when binding the proxy.
+ if (sProviderInstance != null) return sProviderInstance;
- static synchronized WebViewFactoryProvider getProvider() {
- // For now the main purpose of this function (and the factory abstraction) is to keep
- // us honest and minimize usage of WebViewClassic internals when binding the proxy.
- if (sProviderInstance != null) return sProviderInstance;
+ // For debug builds, we allow a system property to specify that we should use the
+ // Chromium powered WebView. This enables us to switch between implementations
+ // at runtime. For user (release) builds, don't allow this.
+ if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("webview.use_chromium", false)) {
+ StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+ try {
+ sProviderInstance = loadChromiumProvider();
+ if (DEBUG) Log.v(LOGTAG, "Loaded Chromium provider: " + sProviderInstance);
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
+ }
+ }
- sProviderInstance = getFactoryByName(DEFAULT_WEB_VIEW_FACTORY);
- if (sProviderInstance == null) {
- if (DEBUG) Log.v(LOGTAG, "Falling back to explicit linkage");
- sProviderInstance = new WebViewClassic.Factory();
+ if (sProviderInstance == null) {
+ if (DEBUG) Log.v(LOGTAG, "Falling back to default provider: "
+ + DEFAULT_WEBVIEW_FACTORY);
+ sProviderInstance = getFactoryByName(DEFAULT_WEBVIEW_FACTORY,
+ WebViewFactory.class.getClassLoader());
+ if (sProviderInstance == null) {
+ if (DEBUG) Log.v(LOGTAG, "Falling back to explicit linkage");
+ sProviderInstance = new WebViewClassic.Factory();
+ }
+ }
+ return sProviderInstance;
}
- return sProviderInstance;
}
- private static WebViewFactoryProvider getFactoryByName(String providerName) {
+ // TODO: This allows us to have the legacy and Chromium WebView coexist for development
+ // and side-by-side testing. After transition, remove this when no longer required.
+ private static WebViewFactoryProvider loadChromiumProvider() {
+ ClassLoader clazzLoader = new PathClassLoader(CHROMIUM_WEBVIEW_JAR, null,
+ WebViewFactory.class.getClassLoader());
+ return getFactoryByName(CHROMIUM_WEBVIEW_FACTORY, clazzLoader);
+ }
+
+ private static WebViewFactoryProvider getFactoryByName(String providerName,
+ ClassLoader loader) {
try {
if (DEBUG) Log.v(LOGTAG, "attempt to load class " + providerName);
- Class<?> c = Class.forName(providerName);
+ Class<?> c = Class.forName(providerName, true, loader);
if (DEBUG) Log.v(LOGTAG, "instantiating factory");
return (WebViewFactoryProvider) c.newInstance();
} catch (ClassNotFoundException e) {
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 1d302f1..934ef83 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -42,6 +42,12 @@ public interface WebViewFactoryProvider {
* {@link android.webkit.WebView#disablePlatformNotifications()}
*/
void setPlatformNotificationsEnabled(boolean enable);
+
+ /**
+ * Implements the API method:
+ * {@link android.webkit.WebSettings#getDefaultUserAgent(Context) }
+ */
+ String getDefaultUserAgent(Context context);
}
Statics getStatics();
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 867ee54..c9f9fbd 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -37,6 +37,7 @@ import android.view.inputmethod.InputConnection;
import android.webkit.WebView.HitTestResult;
import android.webkit.WebView.PictureListener;
+import java.io.BufferedWriter;
import java.io.File;
import java.util.Map;
@@ -220,8 +221,6 @@ public interface WebViewProvider {
public WebSettings getSettings();
- public void emulateShiftHeld();
-
public void setMapTrackballToArrowKeys(boolean setMap);
public void flingScroll(int vx, int vy);
@@ -236,7 +235,9 @@ public interface WebViewProvider {
public boolean zoomOut();
- public void debugDump();
+ public void dumpViewHierarchyWithProperties(BufferedWriter out, int level);
+
+ public View findHierarchyView(String className, int hashCode);
//-------------------------------------------------------------------------
// Provider glue methods
diff --git a/core/java/android/webkit/ZoomControlEmbedded.java b/core/java/android/webkit/ZoomControlEmbedded.java
index d2a0561..ae19832 100644
--- a/core/java/android/webkit/ZoomControlEmbedded.java
+++ b/core/java/android/webkit/ZoomControlEmbedded.java
@@ -90,7 +90,7 @@ class ZoomControlEmbedded implements ZoomControlBase {
View controls = mZoomButtonsController.getZoomControls();
ViewGroup.LayoutParams params = controls.getLayoutParams();
if (params instanceof FrameLayout.LayoutParams) {
- ((FrameLayout.LayoutParams) params).gravity = Gravity.RIGHT;
+ ((FrameLayout.LayoutParams) params).gravity = Gravity.END;
}
}
return mZoomButtonsController;
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index 80a6782..1d864e5 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -287,6 +287,7 @@ class ZoomManager {
if (!exceedsMinScaleIncrement(mMinZoomScale, mMaxZoomScale)) {
mMaxZoomScale = mMinZoomScale;
}
+ sanitizeMinMaxScales();
}
public final float getScale() {
@@ -909,6 +910,14 @@ class ZoomManager {
}
}
+ private void sanitizeMinMaxScales() {
+ if (mMinZoomScale > mMaxZoomScale) {
+ Log.w(LOGTAG, "mMinZoom > mMaxZoom!!! " + mMinZoomScale + " > " + mMaxZoomScale,
+ new Exception());
+ mMaxZoomScale = mMinZoomScale;
+ }
+ }
+
public void onSizeChanged(int w, int h, int ow, int oh) {
// reset zoom and anchor to the top left corner of the screen
// unless we are already zooming
@@ -933,6 +942,7 @@ class ZoomManager {
if (mInitialScale > 0 && mInitialScale < mMinZoomScale) {
mMinZoomScale = mInitialScale;
}
+ sanitizeMinMaxScales();
}
dismissZoomPicker();
@@ -1004,6 +1014,7 @@ class ZoomManager {
} else {
mMaxZoomScale = viewState.mMaxScale;
}
+ sanitizeMinMaxScales();
}
/**
@@ -1033,6 +1044,7 @@ class ZoomManager {
if (!mMinZoomScaleFixed || settings.getUseWideViewPort()) {
mMinZoomScale = newZoomOverviewScale;
mMaxZoomScale = Math.max(mMaxZoomScale, mMinZoomScale);
+ sanitizeMinMaxScales();
}
// fit the content width to the current view for the first new picture
// after first layout.
@@ -1113,6 +1125,7 @@ class ZoomManager {
mMinZoomScale = (mInitialScale > 0) ?
Math.min(mInitialScale, overviewScale) : overviewScale;
mMaxZoomScale = Math.max(mMaxZoomScale, mMinZoomScale);
+ sanitizeMinMaxScales();
}
if (!mWebView.drawHistory()) {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 437da59..7f0af09 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -40,7 +40,6 @@ import android.util.SparseBooleanArray;
import android.util.StateSet;
import android.view.ActionMode;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.FocusFinder;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
@@ -66,6 +65,7 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionWrapper;
import android.view.inputmethod.InputMethodManager;
+import android.widget.RemoteViews.OnClickHandler;
import com.android.internal.R;
@@ -676,6 +676,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
static final Interpolator sLinearInterpolator = new LinearInterpolator();
/**
+ * The saved state that we will be restoring from when we next sync.
+ * Kept here so that if we happen to be asked to save our state before
+ * the sync happens, we can return this existing data rather than losing
+ * it.
+ */
+ private SavedState mPendingSync;
+
+ /**
* Interface definition for a callback to be invoked when the list or grid
* has been scrolled.
*/
@@ -974,6 +982,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Start selection mode if needed. We don't need to if we're unchecking something.
if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
+ if (mMultiChoiceModeCallback == null ||
+ !mMultiChoiceModeCallback.hasWrappedCallback()) {
+ throw new IllegalStateException("AbsListView: attempted to start selection mode " +
+ "for CHOICE_MODE_MULTIPLE_MODAL but no choice mode callback was " +
+ "supplied. Call setMultiChoiceModeListener to set a callback.");
+ }
mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
}
@@ -1329,150 +1343,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
@Override
- public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
- if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
- switch(direction) {
- case ACCESSIBILITY_FOCUS_BACKWARD: {
- View focusable = (getChildCount() > 0) ? getChildAt(getChildCount() - 1) : this;
- if (focusable.isAccessibilityFocusable()) {
- views.add(focusable);
- }
- } return;
- case ACCESSIBILITY_FOCUS_FORWARD: {
- if (isAccessibilityFocusable()) {
- views.add(this);
- }
- } return;
- }
- }
- super.addFocusables(views, direction, focusableMode);
- }
-
- @Override
- public View focusSearch(int direction) {
- return focusSearch(this, direction);
- }
-
- @Override
- public View focusSearch(View focused, int direction) {
- switch (direction) {
- case ACCESSIBILITY_FOCUS_FORWARD: {
- // If we are the focused view try giving it to the first child.
- if (focused == this) {
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() == View.VISIBLE) {
- return child;
- }
- }
- return super.focusSearch(this, direction);
- }
- // Find the item that has the focused view.
- final int currentPosition = getPositionForView(focused);
- if (currentPosition < 0 || currentPosition >= getCount()) {
- return super.focusSearch(this, direction);
- }
- // Try to advance focus in the current item.
- View currentItem = getChildAt(currentPosition - getFirstVisiblePosition());
- if (currentItem.getVisibility() == View.VISIBLE) {
- if (currentItem instanceof ViewGroup) {
- ViewGroup currentItemGroup = (ViewGroup) currentItem;
- View nextFocus = FocusFinder.getInstance().findNextFocus(currentItemGroup,
- focused, direction);
- if (nextFocus != null && nextFocus != currentItemGroup
- && nextFocus != focused) {
- return nextFocus;
- }
- }
- }
- // Try to move focus to the next item.
- final int nextPosition = currentPosition - getFirstVisiblePosition() + 1;
- for (int i = nextPosition; i < getChildCount(); i++) {
- View child = getChildAt(i);
- if (child.getVisibility() == View.VISIBLE) {
- return child;
- }
- }
- // No next item start searching from the list.
- return super.focusSearch(this, direction);
- }
- case ACCESSIBILITY_FOCUS_BACKWARD: {
- // If we are the focused search from the view that is
- // as closer to the bottom as possible.
- if (focused == this) {
- final int childCount = getChildCount();
- for (int i = childCount - 1; i >= 0; i--) {
- View child = getChildAt(i);
- if (child.getVisibility() == View.VISIBLE) {
- return super.focusSearch(child, direction);
- }
- }
- return super.focusSearch(this, direction);
- }
- // Find the item that has the focused view.
- final int currentPosition = getPositionForView(focused);
- if (currentPosition < 0 || currentPosition >= getCount()) {
- return super.focusSearch(this, direction);
- }
-
- View currentItem = getChildAt(currentPosition - getFirstVisiblePosition());
-
- // If a list item is the focused view we try to find a view
- // in the previous item since in reverse the item contents
- // get accessibility focus before the item itself.
- if (currentItem == focused) {
- currentItem = null;
- focused = null;
- // This list gets accessibility focus after the last item.
- final int previousPosition = currentPosition - getFirstVisiblePosition() - 1;
- for (int i = previousPosition; i >= 0; i--) {
- View child = getChildAt(i);
- if (child.getVisibility() == View.VISIBLE) {
- currentItem = child;
- break;
- }
- }
- if (currentItem == null) {
- return this;
- }
- }
-
- if (currentItem.getVisibility() == View.VISIBLE) {
- // Search into the item.
- if (currentItem instanceof ViewGroup) {
- ViewGroup currentItemGroup = (ViewGroup) currentItem;
- View nextFocus = FocusFinder.getInstance().findNextFocus(currentItemGroup,
- focused, direction);
- if (nextFocus != null && nextFocus != currentItemGroup
- && nextFocus != focused) {
- return nextFocus;
- }
- }
-
- // If not item content wants focus we give it to the item.
- return currentItem;
- }
-
- return super.focusSearch(this, direction);
- }
- }
- return super.focusSearch(focused, direction);
- }
-
- /**
- * @hide
- */
- @Override
- public View findViewToTakeAccessibilityFocusFromHover(View child, View descendant) {
- final int position = getPositionForView(child);
- if (position != INVALID_POSITION) {
- return getChildAt(position - mFirstPosition);
- }
- return super.findViewToTakeAccessibilityFocusFromHover(child, descendant);
- }
-
- @Override
public void sendAccessibilityEvent(int eventType) {
// Since this class calls onScrollChanged even if the mFirstPosition and the
// child count have not changed we will avoid sending duplicate accessibility
@@ -1751,6 +1621,21 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
SavedState ss = new SavedState(superState);
+ if (mPendingSync != null) {
+ // Just keep what we last restored.
+ ss.selectedId = mPendingSync.selectedId;
+ ss.firstId = mPendingSync.firstId;
+ ss.viewTop = mPendingSync.viewTop;
+ ss.position = mPendingSync.position;
+ ss.height = mPendingSync.height;
+ ss.filter = mPendingSync.filter;
+ ss.inActionMode = mPendingSync.inActionMode;
+ ss.checkedItemCount = mPendingSync.checkedItemCount;
+ ss.checkState = mPendingSync.checkState;
+ ss.checkIdState = mPendingSync.checkIdState;
+ return ss;
+ }
+
boolean haveChildren = getChildCount() > 0 && mItemCount > 0;
long selectedId = getSelectedItemId();
ss.selectedId = selectedId;
@@ -1831,6 +1716,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (ss.selectedId >= 0) {
mNeedSync = true;
+ mPendingSync = ss;
mSyncRowId = ss.selectedId;
mSyncPosition = ss.position;
mSpecificTop = ss.viewTop;
@@ -1841,6 +1727,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
setNextSelectedPositionInt(INVALID_POSITION);
mSelectorPosition = INVALID_POSITION;
mNeedSync = true;
+ mPendingSync = ss;
mSyncRowId = ss.firstId;
mSyncPosition = ss.position;
mSpecificTop = ss.viewTop;
@@ -1942,6 +1829,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mDataChanged = false;
mPositionScrollAfterLayout = null;
mNeedSync = false;
+ mPendingSync = null;
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
setSelectedPositionInt(INVALID_POSITION);
@@ -2297,7 +2185,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mAccessibilityDelegate == null) {
mAccessibilityDelegate = new ListItemAccessibilityDelegate();
}
- child.setAccessibilityDelegate(mAccessibilityDelegate);
+ if (child.getAccessibilityDelegate() == null) {
+ child.setAccessibilityDelegate(mAccessibilityDelegate);
+ }
}
return child;
@@ -5346,6 +5236,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mNeedSync) {
// Update this first, since setNextSelectedPositionInt inspects it
mNeedSync = false;
+ mPendingSync = null;
if (mTranscriptMode == TRANSCRIPT_MODE_ALWAYS_SCROLL) {
mLayoutMode = LAYOUT_FORCE_BOTTOM;
@@ -5461,6 +5352,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mNextSelectedPosition = INVALID_POSITION;
mNextSelectedRowId = INVALID_ROW_ID;
mNeedSync = false;
+ mPendingSync = null;
mSelectorPosition = INVALID_POSITION;
checkSelectionChanged();
}
@@ -5984,6 +5876,21 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
/**
+ * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
+ *
+ * @param handler The OnClickHandler to use when inflating RemoteViews.
+ *
+ * @hide
+ */
+ public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
+ // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
+ // service handling the specified intent.
+ if (mRemoteAdapter != null) {
+ mRemoteAdapter.setRemoteViewsOnClickHandler(handler);
+ }
+ }
+
+ /**
* This defers a notifyDataSetChanged on the pending RemoteViewsAdapter if it has not
* connected yet.
*/
@@ -6090,6 +5997,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mWrapped = wrapped;
}
+ public boolean hasWrappedCallback() {
+ return mWrapped != null;
+ }
+
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
if (mWrapped.onCreateActionMode(mode, menu)) {
// Initialize checked graphic state?
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index e217e4f..646fe7e 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -107,6 +107,9 @@ public abstract class AbsSeekBar extends ProgressBar {
}
if (thumb != null) {
thumb.setCallback(this);
+ if (canResolveLayoutDirection()) {
+ thumb.setLayoutDirection(getLayoutDirection());
+ }
// Assuming the thumb drawable is symmetric, set the thumb offset
// such that the thumb will hang halfway off either edge of the
@@ -301,9 +304,22 @@ public abstract class AbsSeekBar extends ProgressBar {
}
// Canvas will be translated, so 0,0 is where we start drawing
- thumb.setBounds(thumbPos, topBound, thumbPos + thumbWidth, bottomBound);
+ final int left = isLayoutRtl() ? available - thumbPos : thumbPos;
+ thumb.setBounds(left, topBound, left + thumbWidth, bottomBound);
}
-
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onResolveDrawables(int layoutDirection) {
+ super.onResolveDrawables(layoutDirection);
+
+ if (mThumb != null) {
+ mThumb.setLayoutDirection(layoutDirection);
+ }
+ }
+
@Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -409,15 +425,25 @@ public abstract class AbsSeekBar extends ProgressBar {
int x = (int)event.getX();
float scale;
float progress = 0;
- if (x < mPaddingLeft) {
- scale = 0.0f;
- } else if (x > width - mPaddingRight) {
- scale = 1.0f;
+ if (isLayoutRtl()) {
+ if (x > width - mPaddingRight) {
+ scale = 0.0f;
+ } else if (x < mPaddingLeft) {
+ scale = 1.0f;
+ } else {
+ scale = (float)(available - x + mPaddingLeft) / (float)available;
+ progress = mTouchProgressOffset;
+ }
} else {
- scale = (float)(x - mPaddingLeft) / (float)available;
- progress = mTouchProgressOffset;
+ if (x < mPaddingLeft) {
+ scale = 0.0f;
+ } else if (x > width - mPaddingRight) {
+ scale = 1.0f;
+ } else {
+ scale = (float)(x - mPaddingLeft) / (float)available;
+ progress = mTouchProgressOffset;
+ }
}
-
final int max = getMax();
progress += scale * max;
diff --git a/core/java/android/widget/ActivityChooserModel.java b/core/java/android/widget/ActivityChooserModel.java
index fe6c4f5..736566e 100644
--- a/core/java/android/widget/ActivityChooserModel.java
+++ b/core/java/android/widget/ActivityChooserModel.java
@@ -21,7 +21,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.database.DataSetObservable;
-import android.database.DataSetObserver;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
@@ -458,13 +457,18 @@ public class ActivityChooserModel extends DataSetObservable {
* </p>
*
* @return An {@link Intent} for launching the activity or null if the
- * policy has consumed the intent.
+ * policy has consumed the intent or there is not current intent
+ * set via {@link #setIntent(Intent)}.
*
* @see HistoricalRecord
* @see OnChooseActivityListener
*/
public Intent chooseActivity(int index) {
synchronized (mInstanceLock) {
+ if (mIntent == null) {
+ return null;
+ }
+
ensureConsistentState();
ActivityResolveInfo chosenActivity = mActivities.get(index);
diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java
index 4eb169b..2037c3a 100644
--- a/core/java/android/widget/ActivityChooserView.java
+++ b/core/java/android/widget/ActivityChooserView.java
@@ -497,7 +497,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
// Default activity button.
final int activityCount = mAdapter.getActivityCount();
final int historySize = mAdapter.getHistorySize();
- if (activityCount > 0 && historySize > 0) {
+ if (activityCount==1 || activityCount > 1 && historySize > 0) {
mDefaultActivityButton.setVisibility(VISIBLE);
ResolveInfo activity = mAdapter.getDefaultActivity();
PackageManager packageManager = mContext.getPackageManager();
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 2266cea..90e949a 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -31,6 +31,7 @@ import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.RemoteViews.OnClickHandler;
import java.util.ArrayList;
import java.util.HashMap;
@@ -992,6 +993,21 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
}
}
+ /**
+ * Sets up the onClickHandler to be used by the RemoteViewsAdapter when inflating RemoteViews
+ *
+ * @param handler The OnClickHandler to use when inflating RemoteViews.
+ *
+ * @hide
+ */
+ public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
+ // Ensure that we don't already have a RemoteViewsAdapter that is bound to an existing
+ // service handling the specified intent.
+ if (mRemoteViewsAdapter != null) {
+ mRemoteViewsAdapter.setRemoteViewsOnClickHandler(handler);
+ }
+ }
+
@Override
public void setSelection(int position) {
setDisplayedChild(position);
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index 988760d..06dadb0 100755
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -18,17 +18,23 @@ package android.widget;
import com.android.internal.R;
+import android.app.AlertDialog;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageParser;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import java.text.Collator;
import java.util.ArrayList;
@@ -36,7 +42,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -52,102 +57,270 @@ import java.util.Set;
*
* {@hide}
*/
-public class AppSecurityPermissions implements View.OnClickListener {
+public class AppSecurityPermissions {
- private enum State {
- NO_PERMS,
- DANGEROUS_ONLY,
- NORMAL_ONLY,
- BOTH
- }
+ public static final int WHICH_PERSONAL = 1<<0;
+ public static final int WHICH_DEVICE = 1<<1;
+ public static final int WHICH_NEW = 1<<2;
+ public static final int WHICH_ALL = 0xffff;
private final static String TAG = "AppSecurityPermissions";
- private boolean localLOGV = false;
+ private final static boolean localLOGV = false;
private Context mContext;
private LayoutInflater mInflater;
private PackageManager mPm;
- private LinearLayout mPermsView;
- private Map<String, String> mDangerousMap;
- private Map<String, String> mNormalMap;
- private List<PermissionInfo> mPermsList;
- private String mDefaultGrpLabel;
- private String mDefaultGrpName="DefaultGrp";
- private String mPermFormat;
+ private PackageInfo mInstalledPackageInfo;
+ private final Map<String, MyPermissionGroupInfo> mPermGroups
+ = new HashMap<String, MyPermissionGroupInfo>();
+ private final List<MyPermissionGroupInfo> mPermGroupsList
+ = new ArrayList<MyPermissionGroupInfo>();
+ private final PermissionGroupInfoComparator mPermGroupComparator;
+ private final PermissionInfoComparator mPermComparator;
+ private List<MyPermissionInfo> mPermsList;
+ private CharSequence mNewPermPrefix;
private Drawable mNormalIcon;
private Drawable mDangerousIcon;
- private boolean mExpanded;
- private Drawable mShowMaxIcon;
- private Drawable mShowMinIcon;
- private View mShowMore;
- private TextView mShowMoreText;
- private ImageView mShowMoreIcon;
- private State mCurrentState;
- private LinearLayout mNonDangerousList;
- private LinearLayout mDangerousList;
- private HashMap<String, CharSequence> mGroupLabelCache;
- private View mNoPermsView;
-
+
+ static class MyPermissionGroupInfo extends PermissionGroupInfo {
+ CharSequence mLabel;
+
+ final ArrayList<MyPermissionInfo> mNewPermissions = new ArrayList<MyPermissionInfo>();
+ final ArrayList<MyPermissionInfo> mPersonalPermissions = new ArrayList<MyPermissionInfo>();
+ final ArrayList<MyPermissionInfo> mDevicePermissions = new ArrayList<MyPermissionInfo>();
+ final ArrayList<MyPermissionInfo> mAllPermissions = new ArrayList<MyPermissionInfo>();
+
+ MyPermissionGroupInfo(PermissionInfo perm) {
+ name = perm.packageName;
+ packageName = perm.packageName;
+ }
+
+ MyPermissionGroupInfo(PermissionGroupInfo info) {
+ super(info);
+ }
+
+ public Drawable loadGroupIcon(PackageManager pm) {
+ if (icon != 0) {
+ return loadIcon(pm);
+ } else {
+ ApplicationInfo appInfo;
+ try {
+ appInfo = pm.getApplicationInfo(packageName, 0);
+ return appInfo.loadIcon(pm);
+ } catch (NameNotFoundException e) {
+ }
+ }
+ return null;
+ }
+ }
+
+ static class MyPermissionInfo extends PermissionInfo {
+ CharSequence mLabel;
+
+ /**
+ * PackageInfo.requestedPermissionsFlags for the new package being installed.
+ */
+ int mNewReqFlags;
+
+ /**
+ * PackageInfo.requestedPermissionsFlags for the currently installed
+ * package, if it is installed.
+ */
+ int mExistingReqFlags;
+
+ /**
+ * True if this should be considered a new permission.
+ */
+ boolean mNew;
+
+ MyPermissionInfo() {
+ }
+
+ MyPermissionInfo(PermissionInfo info) {
+ super(info);
+ }
+
+ MyPermissionInfo(MyPermissionInfo info) {
+ super(info);
+ mNewReqFlags = info.mNewReqFlags;
+ mExistingReqFlags = info.mExistingReqFlags;
+ mNew = info.mNew;
+ }
+ }
+
+ public static class PermissionItemView extends LinearLayout implements View.OnClickListener {
+ MyPermissionGroupInfo mGroup;
+ MyPermissionInfo mPerm;
+ AlertDialog mDialog;
+
+ public PermissionItemView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setClickable(true);
+ }
+
+ public void setPermission(MyPermissionGroupInfo grp, MyPermissionInfo perm,
+ boolean first, CharSequence newPermPrefix) {
+ mGroup = grp;
+ mPerm = perm;
+
+ ImageView permGrpIcon = (ImageView) findViewById(R.id.perm_icon);
+ TextView permNameView = (TextView) findViewById(R.id.perm_name);
+
+ PackageManager pm = getContext().getPackageManager();
+ Drawable icon = null;
+ if (first) {
+ icon = grp.loadGroupIcon(pm);
+ }
+ CharSequence label = perm.mLabel;
+ if (perm.mNew && newPermPrefix != null) {
+ // If this is a new permission, format it appropriately.
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ Parcel parcel = Parcel.obtain();
+ TextUtils.writeToParcel(newPermPrefix, parcel, 0);
+ parcel.setDataPosition(0);
+ CharSequence newStr = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+ builder.append(newStr);
+ builder.append(label);
+ label = builder;
+ }
+
+ permGrpIcon.setImageDrawable(icon);
+ permNameView.setText(label);
+ setOnClickListener(this);
+ if (localLOGV) Log.i(TAG, "Made perm item " + perm.name
+ + ": " + label + " in group " + grp.name);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mGroup != null && mPerm != null) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ PackageManager pm = getContext().getPackageManager();
+ AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ builder.setTitle(mGroup.mLabel);
+ if (mPerm.descriptionRes != 0) {
+ builder.setMessage(mPerm.loadDescription(pm));
+ } else {
+ CharSequence appName;
+ try {
+ ApplicationInfo app = pm.getApplicationInfo(mPerm.packageName, 0);
+ appName = app.loadLabel(pm);
+ } catch (NameNotFoundException e) {
+ appName = mPerm.packageName;
+ }
+ StringBuilder sbuilder = new StringBuilder(128);
+ sbuilder.append(getContext().getString(
+ R.string.perms_description_app, appName));
+ sbuilder.append("\n\n");
+ sbuilder.append(mPerm.name);
+ builder.setMessage(sbuilder.toString());
+ }
+ builder.setCancelable(true);
+ builder.setIcon(mGroup.loadGroupIcon(pm));
+ mDialog = builder.show();
+ mDialog.setCanceledOnTouchOutside(true);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
+ }
+ }
+
public AppSecurityPermissions(Context context, List<PermissionInfo> permList) {
mContext = context;
mPm = mContext.getPackageManager();
- mPermsList = permList;
+ loadResources();
+ mPermComparator = new PermissionInfoComparator();
+ mPermGroupComparator = new PermissionGroupInfoComparator();
+ for (PermissionInfo pi : permList) {
+ mPermsList.add(new MyPermissionInfo(pi));
+ }
+ setPermissions(mPermsList);
}
public AppSecurityPermissions(Context context, String packageName) {
mContext = context;
mPm = mContext.getPackageManager();
- mPermsList = new ArrayList<PermissionInfo>();
- Set<PermissionInfo> permSet = new HashSet<PermissionInfo>();
+ loadResources();
+ mPermComparator = new PermissionInfoComparator();
+ mPermGroupComparator = new PermissionGroupInfoComparator();
+ mPermsList = new ArrayList<MyPermissionInfo>();
+ Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
PackageInfo pkgInfo;
try {
pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
} catch (NameNotFoundException e) {
- Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName);
+ Log.w(TAG, "Couldn't retrieve permissions for package:"+packageName);
return;
}
// Extract all user permissions
if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) {
getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet);
}
- for(PermissionInfo tmpInfo : permSet) {
+ for(MyPermissionInfo tmpInfo : permSet) {
mPermsList.add(tmpInfo);
}
+ setPermissions(mPermsList);
}
-
- public AppSecurityPermissions(Context context, PackageParser.Package pkg) {
+
+ public AppSecurityPermissions(Context context, PackageInfo info) {
mContext = context;
mPm = mContext.getPackageManager();
- mPermsList = new ArrayList<PermissionInfo>();
- Set<PermissionInfo> permSet = new HashSet<PermissionInfo>();
- if(pkg == null) {
+ loadResources();
+ mPermComparator = new PermissionInfoComparator();
+ mPermGroupComparator = new PermissionGroupInfoComparator();
+ mPermsList = new ArrayList<MyPermissionInfo>();
+ Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
+ if(info == null) {
return;
}
+
+ // Convert to a PackageInfo
+ PackageInfo installedPkgInfo = null;
// Get requested permissions
- if (pkg.requestedPermissions != null) {
- ArrayList<String> strList = pkg.requestedPermissions;
- int size = strList.size();
- if (size > 0) {
- extractPerms(strList.toArray(new String[size]), permSet);
+ if (info.requestedPermissions != null) {
+ try {
+ installedPkgInfo = mPm.getPackageInfo(info.packageName,
+ PackageManager.GET_PERMISSIONS);
+ } catch (NameNotFoundException e) {
}
+ extractPerms(info, permSet, installedPkgInfo);
}
// Get permissions related to shared user if any
- if(pkg.mSharedUserId != null) {
+ if (info.sharedUserId != null) {
int sharedUid;
try {
- sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId);
+ sharedUid = mPm.getUidForSharedUser(info.sharedUserId);
getAllUsedPermissions(sharedUid, permSet);
} catch (NameNotFoundException e) {
- Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName);
+ Log.w(TAG, "Could'nt retrieve shared user id for:"+info.packageName);
}
}
// Retrieve list of permissions
- for(PermissionInfo tmpInfo : permSet) {
+ for (MyPermissionInfo tmpInfo : permSet) {
mPermsList.add(tmpInfo);
}
+ setPermissions(mPermsList);
}
-
+
+ private void loadResources() {
+ // Pick up from framework resources instead.
+ mNewPermPrefix = mContext.getText(R.string.perms_new_perm_prefix);
+ mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot);
+ mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission);
+ }
+
/**
- * Utility to retrieve a view displaying a single permission.
+ * Utility to retrieve a view displaying a single permission. This provides
+ * the old UI layout for permissions; it is only here for the device admin
+ * settings to continue to use.
*/
public static View getPermissionItemView(Context context,
CharSequence grpName, CharSequence description, boolean dangerous) {
@@ -155,11 +328,15 @@ public class AppSecurityPermissions implements View.OnClickListener {
Context.LAYOUT_INFLATER_SERVICE);
Drawable icon = context.getResources().getDrawable(dangerous
? R.drawable.ic_bullet_key_permission : R.drawable.ic_text_dot);
- return getPermissionItemView(context, inflater, grpName,
+ return getPermissionItemViewOld(context, inflater, grpName,
description, dangerous, icon);
}
- private void getAllUsedPermissions(int sharedUid, Set<PermissionInfo> permSet) {
+ public PackageInfo getInstalledPackageInfo() {
+ return mInstalledPackageInfo;
+ }
+
+ private void getAllUsedPermissions(int sharedUid, Set<MyPermissionInfo> permSet) {
String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
if(sharedPkgList == null || (sharedPkgList.length == 0)) {
return;
@@ -170,29 +347,95 @@ public class AppSecurityPermissions implements View.OnClickListener {
}
private void getPermissionsForPackage(String packageName,
- Set<PermissionInfo> permSet) {
+ Set<MyPermissionInfo> permSet) {
PackageInfo pkgInfo;
try {
pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
} catch (NameNotFoundException e) {
- Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName);
+ Log.w(TAG, "Couldn't retrieve permissions for package:"+packageName);
return;
}
if ((pkgInfo != null) && (pkgInfo.requestedPermissions != null)) {
- extractPerms(pkgInfo.requestedPermissions, permSet);
+ extractPerms(pkgInfo, permSet, pkgInfo);
}
}
-
- private void extractPerms(String strList[], Set<PermissionInfo> permSet) {
- if((strList == null) || (strList.length == 0)) {
+
+ private void extractPerms(PackageInfo info, Set<MyPermissionInfo> permSet,
+ PackageInfo installedPkgInfo) {
+ String[] strList = info.requestedPermissions;
+ int[] flagsList = info.requestedPermissionsFlags;
+ if ((strList == null) || (strList.length == 0)) {
return;
}
- for(String permName:strList) {
+ mInstalledPackageInfo = installedPkgInfo;
+ for (int i=0; i<strList.length; i++) {
+ String permName = strList[i];
+ // If we are only looking at an existing app, then we only
+ // care about permissions that have actually been granted to it.
+ if (installedPkgInfo != null && info == installedPkgInfo) {
+ if ((flagsList[i]&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) {
+ continue;
+ }
+ }
try {
PermissionInfo tmpPermInfo = mPm.getPermissionInfo(permName, 0);
- if(tmpPermInfo != null) {
- permSet.add(tmpPermInfo);
+ if (tmpPermInfo == null) {
+ continue;
+ }
+ int existingIndex = -1;
+ if (installedPkgInfo != null
+ && installedPkgInfo.requestedPermissions != null) {
+ for (int j=0; j<installedPkgInfo.requestedPermissions.length; j++) {
+ if (permName.equals(installedPkgInfo.requestedPermissions[j])) {
+ existingIndex = j;
+ break;
+ }
+ }
+ }
+ final int existingFlags = existingIndex >= 0 ?
+ installedPkgInfo.requestedPermissionsFlags[existingIndex] : 0;
+ if (!isDisplayablePermission(tmpPermInfo, flagsList[i], existingFlags)) {
+ // This is not a permission that is interesting for the user
+ // to see, so skip it.
+ continue;
+ }
+ final String origGroupName = tmpPermInfo.group;
+ String groupName = origGroupName;
+ if (groupName == null) {
+ groupName = tmpPermInfo.packageName;
+ tmpPermInfo.group = groupName;
+ }
+ MyPermissionGroupInfo group = mPermGroups.get(groupName);
+ if (group == null) {
+ PermissionGroupInfo grp = null;
+ if (origGroupName != null) {
+ grp = mPm.getPermissionGroupInfo(origGroupName, 0);
+ }
+ if (grp != null) {
+ group = new MyPermissionGroupInfo(grp);
+ } else {
+ // We could be here either because the permission
+ // didn't originally specify a group or the group it
+ // gave couldn't be found. In either case, we consider
+ // its group to be the permission's package name.
+ tmpPermInfo.group = tmpPermInfo.packageName;
+ group = mPermGroups.get(tmpPermInfo.group);
+ if (group == null) {
+ group = new MyPermissionGroupInfo(tmpPermInfo);
+ }
+ group = new MyPermissionGroupInfo(tmpPermInfo);
+ }
+ mPermGroups.put(tmpPermInfo.group, group);
}
+ final boolean newPerm = installedPkgInfo != null
+ && (existingFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0;
+ MyPermissionInfo myPerm = new MyPermissionInfo(tmpPermInfo);
+ myPerm.mNewReqFlags = flagsList[i];
+ myPerm.mExistingReqFlags = existingFlags;
+ // This is a new permission if the app is already installed and
+ // doesn't currently hold this permission.
+ myPerm.mNew = newPerm;
+ permSet.add(myPerm);
} catch (NameNotFoundException e) {
Log.i(TAG, "Ignoring unknown permission:"+permName);
}
@@ -200,131 +443,101 @@ public class AppSecurityPermissions implements View.OnClickListener {
}
public int getPermissionCount() {
- return mPermsList.size();
+ return getPermissionCount(WHICH_ALL);
}
- public View getPermissionsView() {
-
- mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mPermsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null);
- mShowMore = mPermsView.findViewById(R.id.show_more);
- mShowMoreIcon = (ImageView) mShowMore.findViewById(R.id.show_more_icon);
- mShowMoreText = (TextView) mShowMore.findViewById(R.id.show_more_text);
- mDangerousList = (LinearLayout) mPermsView.findViewById(R.id.dangerous_perms_list);
- mNonDangerousList = (LinearLayout) mPermsView.findViewById(R.id.non_dangerous_perms_list);
- mNoPermsView = mPermsView.findViewById(R.id.no_permissions);
-
- // Set up the LinearLayout that acts like a list item.
- mShowMore.setClickable(true);
- mShowMore.setOnClickListener(this);
- mShowMore.setFocusable(true);
-
- // Pick up from framework resources instead.
- mDefaultGrpLabel = mContext.getString(R.string.default_permission_group);
- mPermFormat = mContext.getString(R.string.permissions_format);
- mNormalIcon = mContext.getResources().getDrawable(R.drawable.ic_text_dot);
- mDangerousIcon = mContext.getResources().getDrawable(R.drawable.ic_bullet_key_permission);
- mShowMaxIcon = mContext.getResources().getDrawable(R.drawable.expander_close_holo_dark);
- mShowMinIcon = mContext.getResources().getDrawable(R.drawable.expander_open_holo_dark);
-
- // Set permissions view
- setPermissions(mPermsList);
- return mPermsView;
+ private List<MyPermissionInfo> getPermissionList(MyPermissionGroupInfo grp, int which) {
+ if (which == WHICH_NEW) {
+ return grp.mNewPermissions;
+ } else if (which == WHICH_PERSONAL) {
+ return grp.mPersonalPermissions;
+ } else if (which == WHICH_DEVICE) {
+ return grp.mDevicePermissions;
+ } else {
+ return grp.mAllPermissions;
+ }
}
- /**
- * Canonicalizes the group description before it is displayed to the user.
- *
- * TODO check for internationalization issues remove trailing '.' in str1
- */
- private String canonicalizeGroupDesc(String groupDesc) {
- if ((groupDesc == null) || (groupDesc.length() == 0)) {
- return null;
+ public int getPermissionCount(int which) {
+ int N = 0;
+ for (int i=0; i<mPermGroupsList.size(); i++) {
+ N += getPermissionList(mPermGroupsList.get(i), which).size();
}
- // Both str1 and str2 are non-null and are non-zero in size.
- int len = groupDesc.length();
- if(groupDesc.charAt(len-1) == '.') {
- groupDesc = groupDesc.substring(0, len-1);
- }
- return groupDesc;
+ return N;
}
- /**
- * Utility method that concatenates two strings defined by mPermFormat.
- * a null value is returned if both str1 and str2 are null, if one of the strings
- * is null the other non null value is returned without formatting
- * this is to placate initial error checks
- */
- private String formatPermissions(String groupDesc, CharSequence permDesc) {
- if(groupDesc == null) {
- if(permDesc == null) {
- return null;
- }
- return permDesc.toString();
- }
- groupDesc = canonicalizeGroupDesc(groupDesc);
- if(permDesc == null) {
- return groupDesc;
- }
- // groupDesc and permDesc are non null
- return String.format(mPermFormat, groupDesc, permDesc.toString());
+ public View getPermissionsView() {
+ return getPermissionsView(WHICH_ALL);
}
- private CharSequence getGroupLabel(String grpName) {
- if (grpName == null) {
- //return default label
- return mDefaultGrpLabel;
- }
- CharSequence cachedLabel = mGroupLabelCache.get(grpName);
- if (cachedLabel != null) {
- return cachedLabel;
- }
- PermissionGroupInfo pgi;
- try {
- pgi = mPm.getPermissionGroupInfo(grpName, 0);
- } catch (NameNotFoundException e) {
- Log.i(TAG, "Invalid group name:" + grpName);
- return null;
+ public View getPermissionsView(int which) {
+ mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ LinearLayout permsView = (LinearLayout) mInflater.inflate(R.layout.app_perms_summary, null);
+ LinearLayout displayList = (LinearLayout) permsView.findViewById(R.id.perms_list);
+ View noPermsView = permsView.findViewById(R.id.no_permissions);
+
+ displayPermissions(mPermGroupsList, displayList, which);
+ if (displayList.getChildCount() <= 0) {
+ noPermsView.setVisibility(View.VISIBLE);
}
- CharSequence label = pgi.loadLabel(mPm).toString();
- mGroupLabelCache.put(grpName, label);
- return label;
+
+ return permsView;
}
/**
* Utility method that displays permissions from a map containing group name and
* list of permission descriptions.
*/
- private void displayPermissions(boolean dangerous) {
- Map<String, String> permInfoMap = dangerous ? mDangerousMap : mNormalMap;
- LinearLayout permListView = dangerous ? mDangerousList : mNonDangerousList;
+ private void displayPermissions(List<MyPermissionGroupInfo> groups,
+ LinearLayout permListView, int which) {
permListView.removeAllViews();
- Set<String> permInfoStrSet = permInfoMap.keySet();
- for (String loopPermGrpInfoStr : permInfoStrSet) {
- CharSequence grpLabel = getGroupLabel(loopPermGrpInfoStr);
- //guaranteed that grpLabel wont be null since permissions without groups
- //will belong to the default group
- if(localLOGV) Log.i(TAG, "Adding view group:" + grpLabel + ", desc:"
- + permInfoMap.get(loopPermGrpInfoStr));
- permListView.addView(getPermissionItemView(grpLabel,
- permInfoMap.get(loopPermGrpInfoStr), dangerous));
+ int spacing = (int)(8*mContext.getResources().getDisplayMetrics().density);
+
+ for (int i=0; i<groups.size(); i++) {
+ MyPermissionGroupInfo grp = groups.get(i);
+ final List<MyPermissionInfo> perms = getPermissionList(grp, which);
+ for (int j=0; j<perms.size(); j++) {
+ MyPermissionInfo perm = perms.get(j);
+ View view = getPermissionItemView(grp, perm, j == 0,
+ which != WHICH_NEW ? mNewPermPrefix : null);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ if (j == 0) {
+ lp.topMargin = spacing;
+ }
+ if (j == grp.mAllPermissions.size()-1) {
+ lp.bottomMargin = spacing;
+ }
+ if (permListView.getChildCount() == 0) {
+ lp.topMargin *= 2;
+ }
+ permListView.addView(view, lp);
+ }
}
}
- private void displayNoPermissions() {
- mNoPermsView.setVisibility(View.VISIBLE);
+ private PermissionItemView getPermissionItemView(MyPermissionGroupInfo grp,
+ MyPermissionInfo perm, boolean first, CharSequence newPermPrefix) {
+ return getPermissionItemView(mContext, mInflater, grp, perm, first, newPermPrefix);
}
- private View getPermissionItemView(CharSequence grpName, CharSequence permList,
- boolean dangerous) {
- return getPermissionItemView(mContext, mInflater, grpName, permList,
- dangerous, dangerous ? mDangerousIcon : mNormalIcon);
+ private static PermissionItemView getPermissionItemView(Context context, LayoutInflater inflater,
+ MyPermissionGroupInfo grp, MyPermissionInfo perm, boolean first,
+ CharSequence newPermPrefix) {
+ PermissionItemView permView = (PermissionItemView)inflater.inflate(
+ (perm.flags & PermissionInfo.FLAG_COSTS_MONEY) != 0
+ ? R.layout.app_permission_item_money : R.layout.app_permission_item,
+ null);
+ permView.setPermission(grp, perm, first, newPermPrefix);
+ return permView;
}
- private static View getPermissionItemView(Context context, LayoutInflater inflater,
+ private static View getPermissionItemViewOld(Context context, LayoutInflater inflater,
CharSequence grpName, CharSequence permList, boolean dangerous, Drawable icon) {
- View permView = inflater.inflate(R.layout.app_permission_item, null);
+ View permView = inflater.inflate(R.layout.app_permission_item_old, null);
TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group);
TextView permDescView = (TextView) permView.findViewById(R.id.permission_list);
@@ -341,159 +554,109 @@ public class AppSecurityPermissions implements View.OnClickListener {
return permView;
}
- private void showPermissions() {
-
- switch(mCurrentState) {
- case NO_PERMS:
- displayNoPermissions();
- break;
-
- case DANGEROUS_ONLY:
- displayPermissions(true);
- break;
-
- case NORMAL_ONLY:
- displayPermissions(false);
- break;
-
- case BOTH:
- displayPermissions(true);
- if (mExpanded) {
- displayPermissions(false);
- mShowMoreIcon.setImageDrawable(mShowMaxIcon);
- mShowMoreText.setText(R.string.perms_hide);
- mNonDangerousList.setVisibility(View.VISIBLE);
- } else {
- mShowMoreIcon.setImageDrawable(mShowMinIcon);
- mShowMoreText.setText(R.string.perms_show_all);
- mNonDangerousList.setVisibility(View.GONE);
- }
- mShowMore.setVisibility(View.VISIBLE);
- break;
+ private boolean isDisplayablePermission(PermissionInfo pInfo, int newReqFlags,
+ int existingReqFlags) {
+ final int base = pInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
+ // Dangerous and normal permissions are always shown to the user.
+ if (base == PermissionInfo.PROTECTION_DANGEROUS ||
+ base == PermissionInfo.PROTECTION_NORMAL) {
+ return true;
}
- }
-
- private boolean isDisplayablePermission(PermissionInfo pInfo) {
- if(pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS ||
- pInfo.protectionLevel == PermissionInfo.PROTECTION_NORMAL) {
+ // Development permissions are only shown to the user if they are already
+ // granted to the app -- if we are installing an app and they are not
+ // already granted, they will not be granted as part of the install.
+ if ((existingReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0
+ && (pInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
+ if (localLOGV) Log.i(TAG, "Special perm " + pInfo.name
+ + ": protlevel=0x" + Integer.toHexString(pInfo.protectionLevel));
return true;
}
return false;
}
- /*
- * Utility method that aggregates all permission descriptions categorized by group
- * Say group1 has perm11, perm12, perm13, the group description will be
- * perm11_Desc, perm12_Desc, perm13_Desc
- */
- private void aggregateGroupDescs(
- Map<String, List<PermissionInfo> > map, Map<String, String> retMap) {
- if(map == null) {
- return;
+ private static class PermissionGroupInfoComparator implements Comparator<MyPermissionGroupInfo> {
+ private final Collator sCollator = Collator.getInstance();
+ PermissionGroupInfoComparator() {
}
- if(retMap == null) {
- return;
- }
- Set<String> grpNames = map.keySet();
- Iterator<String> grpNamesIter = grpNames.iterator();
- while(grpNamesIter.hasNext()) {
- String grpDesc = null;
- String grpNameKey = grpNamesIter.next();
- List<PermissionInfo> grpPermsList = map.get(grpNameKey);
- if(grpPermsList == null) {
- continue;
- }
- for(PermissionInfo permInfo: grpPermsList) {
- CharSequence permDesc = permInfo.loadLabel(mPm);
- grpDesc = formatPermissions(grpDesc, permDesc);
+ public final int compare(MyPermissionGroupInfo a, MyPermissionGroupInfo b) {
+ if (((a.flags^b.flags)&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) {
+ return ((a.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) ? -1 : 1;
}
- // Insert grpDesc into map
- if(grpDesc != null) {
- if(localLOGV) Log.i(TAG, "Group:"+grpNameKey+" description:"+grpDesc.toString());
- retMap.put(grpNameKey, grpDesc.toString());
+ if (a.priority != b.priority) {
+ return a.priority > b.priority ? -1 : 1;
}
+ return sCollator.compare(a.mLabel, b.mLabel);
}
}
- private static class PermissionInfoComparator implements Comparator<PermissionInfo> {
- private PackageManager mPm;
+ private static class PermissionInfoComparator implements Comparator<MyPermissionInfo> {
private final Collator sCollator = Collator.getInstance();
- PermissionInfoComparator(PackageManager pm) {
- mPm = pm;
+ PermissionInfoComparator() {
}
- public final int compare(PermissionInfo a, PermissionInfo b) {
- CharSequence sa = a.loadLabel(mPm);
- CharSequence sb = b.loadLabel(mPm);
- return sCollator.compare(sa, sb);
+ public final int compare(MyPermissionInfo a, MyPermissionInfo b) {
+ return sCollator.compare(a.mLabel, b.mLabel);
}
}
-
- private void setPermissions(List<PermissionInfo> permList) {
- mGroupLabelCache = new HashMap<String, CharSequence>();
- //add the default label so that uncategorized permissions can go here
- mGroupLabelCache.put(mDefaultGrpName, mDefaultGrpLabel);
-
- // Map containing group names and a list of permissions under that group
- // categorized as dangerous
- mDangerousMap = new HashMap<String, String>();
- // Map containing group names and a list of permissions under that group
- // categorized as normal
- mNormalMap = new HashMap<String, String>();
-
- // Additional structures needed to ensure that permissions are unique under
- // each group
- Map<String, List<PermissionInfo>> dangerousMap =
- new HashMap<String, List<PermissionInfo>>();
- Map<String, List<PermissionInfo> > normalMap =
- new HashMap<String, List<PermissionInfo>>();
- PermissionInfoComparator permComparator = new PermissionInfoComparator(mPm);
-
+
+ private void addPermToList(List<MyPermissionInfo> permList,
+ MyPermissionInfo pInfo) {
+ if (pInfo.mLabel == null) {
+ pInfo.mLabel = pInfo.loadLabel(mPm);
+ }
+ int idx = Collections.binarySearch(permList, pInfo, mPermComparator);
+ if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+permList.size());
+ if (idx < 0) {
+ idx = -idx-1;
+ permList.add(idx, pInfo);
+ }
+ }
+
+ private void setPermissions(List<MyPermissionInfo> permList) {
if (permList != null) {
// First pass to group permissions
- for (PermissionInfo pInfo : permList) {
+ for (MyPermissionInfo pInfo : permList) {
if(localLOGV) Log.i(TAG, "Processing permission:"+pInfo.name);
- if(!isDisplayablePermission(pInfo)) {
+ if(!isDisplayablePermission(pInfo, pInfo.mNewReqFlags, pInfo.mExistingReqFlags)) {
if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" is not displayable");
continue;
}
- Map<String, List<PermissionInfo> > permInfoMap =
- (pInfo.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) ?
- dangerousMap : normalMap;
- String grpName = (pInfo.group == null) ? mDefaultGrpName : pInfo.group;
- if(localLOGV) Log.i(TAG, "Permission:"+pInfo.name+" belongs to group:"+grpName);
- List<PermissionInfo> grpPermsList = permInfoMap.get(grpName);
- if(grpPermsList == null) {
- grpPermsList = new ArrayList<PermissionInfo>();
- permInfoMap.put(grpName, grpPermsList);
- grpPermsList.add(pInfo);
- } else {
- int idx = Collections.binarySearch(grpPermsList, pInfo, permComparator);
- if(localLOGV) Log.i(TAG, "idx="+idx+", list.size="+grpPermsList.size());
- if (idx < 0) {
- idx = -idx-1;
- grpPermsList.add(idx, pInfo);
+ MyPermissionGroupInfo group = mPermGroups.get(pInfo.group);
+ if (group != null) {
+ pInfo.mLabel = pInfo.loadLabel(mPm);
+ addPermToList(group.mAllPermissions, pInfo);
+ if (pInfo.mNew) {
+ addPermToList(group.mNewPermissions, pInfo);
+ }
+ if ((group.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0) {
+ addPermToList(group.mPersonalPermissions, pInfo);
+ } else {
+ addPermToList(group.mDevicePermissions, pInfo);
}
}
}
- // Second pass to actually form the descriptions
- // Look at dangerous permissions first
- aggregateGroupDescs(dangerousMap, mDangerousMap);
- aggregateGroupDescs(normalMap, mNormalMap);
}
- mCurrentState = State.NO_PERMS;
- if(mDangerousMap.size() > 0) {
- mCurrentState = (mNormalMap.size() > 0) ? State.BOTH : State.DANGEROUS_ONLY;
- } else if(mNormalMap.size() > 0) {
- mCurrentState = State.NORMAL_ONLY;
+ for (MyPermissionGroupInfo pgrp : mPermGroups.values()) {
+ if (pgrp.labelRes != 0 || pgrp.nonLocalizedLabel != null) {
+ pgrp.mLabel = pgrp.loadLabel(mPm);
+ } else {
+ ApplicationInfo app;
+ try {
+ app = mPm.getApplicationInfo(pgrp.packageName, 0);
+ pgrp.mLabel = app.loadLabel(mPm);
+ } catch (NameNotFoundException e) {
+ pgrp.mLabel = pgrp.loadLabel(mPm);
+ }
+ }
+ mPermGroupsList.add(pgrp);
+ }
+ Collections.sort(mPermGroupsList, mPermGroupComparator);
+ if (localLOGV) {
+ for (MyPermissionGroupInfo grp : mPermGroupsList) {
+ Log.i(TAG, "Group " + grp.name + " personal="
+ + ((grp.flags&PermissionGroupInfo.FLAG_PERSONAL_INFO) != 0)
+ + " priority=" + grp.priority);
+ }
}
- if(localLOGV) Log.i(TAG, "mCurrentState=" + mCurrentState);
- showPermissions();
- }
-
- public void onClick(View v) {
- if(localLOGV) Log.i(TAG, "mExpanded="+mExpanded);
- mExpanded = !mExpanded;
- showPermissions();
}
}
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 30dd17d..75d1471 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -579,6 +579,23 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
/**
+ * Set a listener that will be invoked whenever the AutoCompleteTextView's
+ * list of completions is dismissed.
+ * @param dismissListener Listener to invoke when completions are dismissed
+ */
+ public void setOnDismissListener(final OnDismissListener dismissListener) {
+ PopupWindow.OnDismissListener wrappedListener = null;
+ if (dismissListener != null) {
+ wrappedListener = new PopupWindow.OnDismissListener() {
+ @Override public void onDismiss() {
+ dismissListener.onDismiss();
+ }
+ };
+ }
+ mPopup.setOnDismissListener(wrappedListener);
+ }
+
+ /**
* <p>Returns a filterable list adapter used for auto completion.</p>
*
* @return a data adapter used for auto completion
@@ -904,8 +921,6 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
*
* @param filter If <code>false</code>, no filtering will be performed
* as a result of this call.
- *
- * @hide Pending API council approval.
*/
public void setText(CharSequence text, boolean filter) {
if (filter) {
@@ -1078,6 +1093,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED);
mPopup.setListItemExpandMax(EXPAND_MAX);
}
+ mPopup.setLayoutDirection(getLayoutDirection());
mPopup.show();
mPopup.getListView().setOverScrollMode(View.OVER_SCROLL_ALWAYS);
}
@@ -1208,6 +1224,19 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
/**
+ * Listener to respond to the AutoCompleteTextView's completion list being dismissed.
+ * @see AutoCompleteTextView#setOnDismissListener(OnDismissListener)
+ */
+ public interface OnDismissListener {
+ /**
+ * This method will be invoked whenever the AutoCompleteTextView's list
+ * of completion options has been dismissed and is no longer available
+ * for user interaction.
+ */
+ void onDismiss();
+ }
+
+ /**
* Allows us a private hook into the on click event without preventing users from setting
* their own click listener.
*/
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index b06da06..361eca4 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -1573,7 +1573,8 @@ public class CalendarView extends FrameLayout {
// If we're showing the week number calculate it based on Monday
int i = 0;
if (mShowWeekNumber) {
- mDayNumbers[0] = Integer.toString(mTempDate.get(Calendar.WEEK_OF_YEAR));
+ mDayNumbers[0] = String.format(Locale.getDefault(), "%d",
+ mTempDate.get(Calendar.WEEK_OF_YEAR));
i++;
}
@@ -1594,7 +1595,8 @@ public class CalendarView extends FrameLayout {
if (mTempDate.before(mMinDate) || mTempDate.after(mMaxDate)) {
mDayNumbers[i] = "";
} else {
- mDayNumbers[i] = Integer.toString(mTempDate.get(Calendar.DAY_OF_MONTH));
+ mDayNumbers[i] = String.format(Locale.getDefault(), "%d",
+ mTempDate.get(Calendar.DAY_OF_MONTH));
}
mTempDate.add(Calendar.DAY_OF_MONTH, 1);
}
@@ -1609,7 +1611,7 @@ public class CalendarView extends FrameLayout {
}
/**
- * Initialize the paint isntances.
+ * Initialize the paint instances.
*/
private void initilaizePaints() {
mDrawPaint.setFakeBoldText(false);
@@ -1620,6 +1622,7 @@ public class CalendarView extends FrameLayout {
mMonthNumDrawPaint.setAntiAlias(true);
mMonthNumDrawPaint.setStyle(Style.FILL);
mMonthNumDrawPaint.setTextAlign(Align.CENTER);
+ mMonthNumDrawPaint.setTextSize(mDateTextSize);
}
/**
@@ -1657,16 +1660,34 @@ public class CalendarView extends FrameLayout {
* @return True if a day was found for the given location.
*/
public boolean getDayFromLocation(float x, Calendar outCalendar) {
- int dayStart = mShowWeekNumber ? mWidth / mNumCells : 0;
- if (x < dayStart || x > mWidth) {
+ final boolean isLayoutRtl = isLayoutRtl();
+
+ int start;
+ int end;
+
+ if (isLayoutRtl) {
+ start = 0;
+ end = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ } else {
+ start = mShowWeekNumber ? mWidth / mNumCells : 0;
+ end = mWidth;
+ }
+
+ if (x < start || x > end) {
outCalendar.clear();
return false;
}
- // Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
- int dayPosition = (int) ((x - dayStart) * mDaysPerWeek
- / (mWidth - dayStart));
+
+ // Selection is (x - start) / (pixels/day) which is (x - start) * day / pixels
+ int dayPosition = (int) ((x - start) * mDaysPerWeek / (end - start));
+
+ if (isLayoutRtl) {
+ dayPosition = mDaysPerWeek - 1 - dayPosition;
+ }
+
outCalendar.setTimeInMillis(mFirstDay.getTimeInMillis());
outCalendar.add(Calendar.DAY_OF_MONTH, dayPosition);
+
return true;
}
@@ -1691,12 +1712,25 @@ public class CalendarView extends FrameLayout {
mTempRect.top = mWeekSeperatorLineWidth;
mTempRect.bottom = mHeight;
- mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
- mTempRect.right = mSelectedLeft - 2;
+
+ final boolean isLayoutRtl = isLayoutRtl();
+
+ if (isLayoutRtl) {
+ mTempRect.left = 0;
+ mTempRect.right = mSelectedLeft - 2;
+ } else {
+ mTempRect.left = mShowWeekNumber ? mWidth / mNumCells : 0;
+ mTempRect.right = mSelectedLeft - 2;
+ }
canvas.drawRect(mTempRect, mDrawPaint);
- mTempRect.left = mSelectedRight + 3;
- mTempRect.right = mWidth;
+ if (isLayoutRtl) {
+ mTempRect.left = mSelectedRight + 3;
+ mTempRect.right = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ } else {
+ mTempRect.left = mSelectedRight + 3;
+ mTempRect.right = mWidth;
+ }
canvas.drawRect(mTempRect, mDrawPaint);
}
@@ -1706,25 +1740,41 @@ public class CalendarView extends FrameLayout {
* @param canvas The canvas to draw on
*/
private void drawWeekNumbersAndDates(Canvas canvas) {
- float textHeight = mDrawPaint.getTextSize();
- int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
- int nDays = mNumCells;
+ final float textHeight = mDrawPaint.getTextSize();
+ final int y = (int) ((mHeight + textHeight) / 2) - mWeekSeperatorLineWidth;
+ final int nDays = mNumCells;
+ final int divisor = 2 * nDays;
mDrawPaint.setTextAlign(Align.CENTER);
mDrawPaint.setTextSize(mDateTextSize);
+
int i = 0;
- int divisor = 2 * nDays;
- if (mShowWeekNumber) {
- mDrawPaint.setColor(mWeekNumberColor);
- int x = mWidth / divisor;
- canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
- i++;
- }
- for (; i < nDays; i++) {
- mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
- : mUnfocusedMonthDateColor);
- int x = (2 * i + 1) * mWidth / divisor;
- canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
+
+ if (isLayoutRtl()) {
+ for (; i < nDays - 1; i++) {
+ mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
+ : mUnfocusedMonthDateColor);
+ int x = (2 * i + 1) * mWidth / divisor;
+ canvas.drawText(mDayNumbers[nDays - 1 - i], x, y, mMonthNumDrawPaint);
+ }
+ if (mShowWeekNumber) {
+ mDrawPaint.setColor(mWeekNumberColor);
+ int x = mWidth - mWidth / divisor;
+ canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
+ }
+ } else {
+ if (mShowWeekNumber) {
+ mDrawPaint.setColor(mWeekNumberColor);
+ int x = mWidth / divisor;
+ canvas.drawText(mDayNumbers[0], x, y, mDrawPaint);
+ i++;
+ }
+ for (; i < nDays; i++) {
+ mMonthNumDrawPaint.setColor(mFocusDay[i] ? mFocusedMonthDateColor
+ : mUnfocusedMonthDateColor);
+ int x = (2 * i + 1) * mWidth / divisor;
+ canvas.drawText(mDayNumbers[i], x, y, mMonthNumDrawPaint);
+ }
}
}
@@ -1744,8 +1794,16 @@ public class CalendarView extends FrameLayout {
}
mDrawPaint.setColor(mWeekSeparatorLineColor);
mDrawPaint.setStrokeWidth(mWeekSeperatorLineWidth);
- float x = mShowWeekNumber ? mWidth / mNumCells : 0;
- canvas.drawLine(x, 0, mWidth, 0, mDrawPaint);
+ float startX;
+ float stopX;
+ if (isLayoutRtl()) {
+ startX = 0;
+ stopX = mShowWeekNumber ? mWidth - mWidth / mNumCells : mWidth;
+ } else {
+ startX = mShowWeekNumber ? mWidth / mNumCells : 0;
+ stopX = mWidth;
+ }
+ canvas.drawLine(startX, 0, stopX, 0, mDrawPaint);
}
/**
@@ -1778,15 +1836,21 @@ public class CalendarView extends FrameLayout {
*/
private void updateSelectionPositions() {
if (mHasSelectedDay) {
+ final boolean isLayoutRtl = isLayoutRtl();
int selectedPosition = mSelectedDay - mFirstDayOfWeek;
if (selectedPosition < 0) {
selectedPosition += 7;
}
- if (mShowWeekNumber) {
+ if (mShowWeekNumber && !isLayoutRtl) {
selectedPosition++;
}
- mSelectedLeft = selectedPosition * mWidth / mNumCells;
- mSelectedRight = (selectedPosition + 1) * mWidth / mNumCells;
+ if (isLayoutRtl) {
+ mSelectedLeft = (mDaysPerWeek - 1 - selectedPosition) * mWidth / mNumCells;
+
+ } else {
+ mSelectedLeft = selectedPosition * mWidth / mNumCells;
+ }
+ mSelectedRight = mSelectedLeft + mWidth / mNumCells;
}
}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 61935c2..e74e37c 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -55,7 +55,7 @@ public class CheckedTextView extends TextView implements Checkable {
}
public CheckedTextView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ this(context, attrs, R.attr.checkedTextViewStyle);
}
public CheckedTextView(Context context, AttributeSet attrs, int defStyle) {
@@ -101,7 +101,7 @@ public class CheckedTextView extends TextView implements Checkable {
/**
* Set the checkmark to a given Drawable, identified by its resourece id. This will be drawn
* when {@link #isChecked()} is true.
- *
+ *
* @param resid The Drawable to use for the checkmark.
*
* @see #setCheckMarkDrawable(Drawable)
@@ -144,14 +144,14 @@ public class CheckedTextView extends TextView implements Checkable {
d.setVisible(getVisibility() == VISIBLE, false);
d.setState(CHECKED_STATE_SET);
setMinHeight(d.getIntrinsicHeight());
-
+
mCheckMarkWidth = d.getIntrinsicWidth();
d.setState(getDrawableState());
} else {
mCheckMarkWidth = 0;
}
mCheckMarkDrawable = d;
- // Do padding resolution. This will call setPadding() and do a requestLayout() if needed.
+ // Do padding resolution. This will call internalSetPadding() and do a requestLayout() if needed.
resolvePadding();
}
@@ -169,28 +169,55 @@ public class CheckedTextView extends TextView implements Checkable {
return mCheckMarkDrawable;
}
+ /**
+ * @hide
+ */
+ @Override
+ protected void internalSetPadding(int left, int top, int right, int bottom) {
+ super.internalSetPadding(left, top, right, bottom);
+ setBasePadding(isLayoutRtl());
+ }
+
@Override
- public void onPaddingChanged(int layoutDirection) {
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ updatePadding();
+ }
+
+ private void updatePadding() {
+ resetPaddingToInitialValues();
int newPadding = (mCheckMarkDrawable != null) ?
mCheckMarkWidth + mBasePadding : mBasePadding;
mNeedRequestlayout |= (mPaddingRight != newPadding);
- mPaddingRight = newPadding;
+ if (isLayoutRtl()) {
+ mPaddingLeft = newPadding;
+ } else {
+ mPaddingRight = newPadding;
+ }
if (mNeedRequestlayout) {
requestLayout();
mNeedRequestlayout = false;
}
}
-
+
@Override
public void setPadding(int left, int top, int right, int bottom) {
super.setPadding(left, top, right, bottom);
- mBasePadding = mPaddingRight;
+ setBasePadding(isLayoutRtl());
}
@Override
public void setPaddingRelative(int start, int top, int end, int bottom) {
super.setPaddingRelative(start, top, end, bottom);
- mBasePadding = getPaddingEnd();
+ setBasePadding(isLayoutRtl());
+ }
+
+ private void setBasePadding(boolean isLayoutRtl) {
+ if (isLayoutRtl) {
+ mBasePadding = mPaddingLeft;
+ } else {
+ mBasePadding = mPaddingRight;
+ }
}
@Override
@@ -213,12 +240,20 @@ public class CheckedTextView extends TextView implements Checkable {
break;
}
- int right = getWidth();
- checkMarkDrawable.setBounds(
- right - mPaddingRight,
- y,
- right - mPaddingRight + mCheckMarkWidth,
- y + height);
+ final boolean isLayoutRtl = isLayoutRtl();
+ final int width = getWidth();
+ final int top = y;
+ final int bottom = top + height;
+ final int left;
+ final int right;
+ if (isLayoutRtl) {
+ left = mBasePadding;
+ right = left + mCheckMarkWidth;
+ } else {
+ right = width - mBasePadding;
+ left = right - mCheckMarkWidth;
+ }
+ checkMarkDrawable.setBounds( left, top, right, bottom);
checkMarkDrawable.draw(canvas);
}
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 0a71c5a..421a324 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -225,26 +225,53 @@ public abstract class CompoundButton extends Button implements Checkable {
}
@Override
+ public int getCompoundPaddingLeft() {
+ int padding = super.getCompoundPaddingLeft();
+ if (!isLayoutRtl()) {
+ final Drawable buttonDrawable = mButtonDrawable;
+ if (buttonDrawable != null) {
+ padding += buttonDrawable.getIntrinsicWidth();
+ }
+ }
+ return padding;
+ }
+
+ @Override
+ public int getCompoundPaddingRight() {
+ int padding = super.getCompoundPaddingRight();
+ if (isLayoutRtl()) {
+ final Drawable buttonDrawable = mButtonDrawable;
+ if (buttonDrawable != null) {
+ padding += buttonDrawable.getIntrinsicWidth();
+ }
+ }
+ return padding;
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final Drawable buttonDrawable = mButtonDrawable;
if (buttonDrawable != null) {
final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
- final int height = buttonDrawable.getIntrinsicHeight();
-
- int y = 0;
+ final int drawableHeight = buttonDrawable.getIntrinsicHeight();
+ final int drawableWidth = buttonDrawable.getIntrinsicWidth();
+ int top = 0;
switch (verticalGravity) {
case Gravity.BOTTOM:
- y = getHeight() - height;
+ top = getHeight() - drawableHeight;
break;
case Gravity.CENTER_VERTICAL:
- y = (getHeight() - height) / 2;
+ top = (getHeight() - drawableHeight) / 2;
break;
}
+ int bottom = top + drawableHeight;
+ int left = isLayoutRtl() ? getWidth() - drawableWidth : 0;
+ int right = isLayoutRtl() ? getWidth() : drawableWidth;
- buttonDrawable.setBounds(0, y, buttonDrawable.getIntrinsicWidth(), y + height);
+ buttonDrawable.setBounds(left, top, right, bottom);
buttonDrawable.draw(canvas);
}
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index ac3bedb..07d3a7a 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -220,7 +220,7 @@ public class DatePicker extends FrameLayout {
// day
mDaySpinner = (NumberPicker) findViewById(R.id.day);
- mDaySpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
+ mDaySpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
mDaySpinner.setOnLongPressUpdateInterval(100);
mDaySpinner.setOnValueChangedListener(onChangeListener);
mDaySpinnerInput = (EditText) mDaySpinner.findViewById(R.id.numberpicker_input);
diff --git a/core/java/android/widget/DigitalClock.java b/core/java/android/widget/DigitalClock.java
index add9d9b..3e9107f 100644
--- a/core/java/android/widget/DigitalClock.java
+++ b/core/java/android/widget/DigitalClock.java
@@ -34,13 +34,17 @@ import java.util.Calendar;
*
* FIXME: implement separate views for hours/minutes/seconds, so
* proportional fonts don't shake rendering
+ *
+ * @deprecated It is recommended you use a {@link TextView} and {@link DateFormat}
+ * to implement the same behavior.
*/
-
+@Deprecated
public class DigitalClock extends TextView {
Calendar mCalendar;
private final static String m12 = "h:mm:ss aa";
private final static String m24 = "k:mm:ss";
+ @SuppressWarnings("FieldCanBeLocal") // We must keep a reference to this observer
private FormatChangeObserver mFormatChangeObserver;
private Runnable mTicker;
@@ -52,17 +56,15 @@ public class DigitalClock extends TextView {
public DigitalClock(Context context) {
super(context);
- initClock(context);
+ initClock();
}
public DigitalClock(Context context, AttributeSet attrs) {
super(context, attrs);
- initClock(context);
+ initClock();
}
- private void initClock(Context context) {
- Resources r = mContext.getResources();
-
+ private void initClock() {
if (mCalendar == null) {
mCalendar = Calendar.getInstance();
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index c29dd58..c67cae6 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -88,9 +88,6 @@ import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView.OnItemClickListener;
-import android.widget.Editor.InputContentType;
-import android.widget.Editor.InputMethodState;
-import android.widget.Editor.SelectionModifierCursorController;
import android.widget.TextView.Drawables;
import android.widget.TextView.OnEditorActionListener;
@@ -292,7 +289,7 @@ public class Editor {
mErrorWasChanged = true;
final Drawables dr = mTextView.mDrawables;
if (dr != null) {
- switch (mTextView.getResolvedLayoutDirection()) {
+ switch (mTextView.getLayoutDirection()) {
default:
case View.LAYOUT_DIRECTION_LTR:
mTextView.setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, icon,
@@ -978,8 +975,8 @@ public class Editor {
mSuggestionsPopupWindow.onParentLostFocus();
}
- // Don't leave us in the middle of a batch edit.
- mTextView.onEndBatchEdit();
+ // Don't leave us in the middle of a batch edit. Same as in onFocusChanged
+ ensureEndedBatchEdit();
}
}
@@ -1801,13 +1798,13 @@ public class Editor {
mTextView.deleteText_internal(dragSourceStart, dragSourceEnd);
// Make sure we do not leave two adjacent spaces.
- CharSequence t = mTextView.getTransformedText(dragSourceStart - 1, dragSourceStart + 1);
- if ( (dragSourceStart == 0 || Character.isSpaceChar(t.charAt(0))) &&
- (dragSourceStart == mTextView.getText().length() ||
- Character.isSpaceChar(t.charAt(1))) ) {
- final int pos = dragSourceStart == mTextView.getText().length() ?
- dragSourceStart - 1 : dragSourceStart;
- mTextView.deleteText_internal(pos, pos + 1);
+ final int prevCharIdx = Math.max(0, dragSourceStart - 1);
+ final int nextCharIdx = Math.min(mTextView.getText().length(), dragSourceStart + 1);
+ if (nextCharIdx > prevCharIdx + 1) {
+ CharSequence t = mTextView.getTransformedText(prevCharIdx, nextCharIdx);
+ if (Character.isSpaceChar(t.charAt(0)) && Character.isSpaceChar(t.charAt(1))) {
+ mTextView.deleteText_internal(prevCharIdx, prevCharIdx + 1);
+ }
}
}
}
@@ -2282,14 +2279,11 @@ public class Editor {
final SuggestionInfo suggestionInfo = mSuggestionInfos[position];
textView.setText(suggestionInfo.text);
- if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) {
- textView.setCompoundDrawablesWithIntrinsicBounds(
- com.android.internal.R.drawable.ic_suggestions_add, 0, 0, 0);
- } else if (suggestionInfo.suggestionIndex == DELETE_TEXT) {
- textView.setCompoundDrawablesWithIntrinsicBounds(
- com.android.internal.R.drawable.ic_suggestions_delete, 0, 0, 0);
+ if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY ||
+ suggestionInfo.suggestionIndex == DELETE_TEXT) {
+ textView.setBackgroundColor(Color.TRANSPARENT);
} else {
- textView.setCompoundDrawables(null, null, null, null);
+ textView.setBackgroundColor(Color.WHITE);
}
return textView;
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index 083a952..d2139af 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -181,10 +181,13 @@ class FastScroller {
}
public void setScrollbarPosition(int position) {
+ if (position == View.SCROLLBAR_POSITION_DEFAULT) {
+ position = mList.isLayoutRtl() ?
+ View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT;
+ }
mPosition = position;
switch (position) {
default:
- case View.SCROLLBAR_POSITION_DEFAULT:
case View.SCROLLBAR_POSITION_RIGHT:
mOverlayDrawable = mOverlayDrawableRight;
break;
@@ -229,7 +232,6 @@ class FastScroller {
final int viewWidth = mList.getWidth();
// Bounds are always top right. Y coordinate get's translated during draw
switch (mPosition) {
- case View.SCROLLBAR_POSITION_DEFAULT:
case View.SCROLLBAR_POSITION_RIGHT:
mThumbDrawable.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH);
break;
@@ -327,7 +329,6 @@ class FastScroller {
}
int left = 0;
switch (mPosition) {
- case View.SCROLLBAR_POSITION_DEFAULT:
case View.SCROLLBAR_POSITION_RIGHT:
left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
break;
@@ -360,7 +361,6 @@ class FastScroller {
int left = 0;
switch (mPosition) {
default:
- case View.SCROLLBAR_POSITION_DEFAULT:
case View.SCROLLBAR_POSITION_RIGHT:
left = Math.max(0,
mThumbDrawable.getBounds().left - mThumbW - mOverlaySize);
@@ -410,7 +410,6 @@ class FastScroller {
if (mThumbDrawable != null) {
switch (mPosition) {
default:
- case View.SCROLLBAR_POSITION_DEFAULT:
case View.SCROLLBAR_POSITION_RIGHT:
mThumbDrawable.setBounds(w - mThumbW, 0, w, mThumbH);
break;
@@ -820,7 +819,6 @@ class FastScroller {
boolean inTrack = false;
switch (mPosition) {
default:
- case View.SCROLLBAR_POSITION_DEFAULT:
case View.SCROLLBAR_POSITION_RIGHT:
inTrack = x > mList.getWidth() - mThumbW;
break;
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index d019d8c..00cd604 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -55,7 +55,7 @@ import android.widget.RemoteViews.RemoteView;
*/
@RemoteView
public class FrameLayout extends ViewGroup {
- private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.LEFT;
+ private static final int DEFAULT_CHILD_GRAVITY = Gravity.TOP | Gravity.START;
@ViewDebug.ExportedProperty(category = "measurement")
boolean mMeasureAllChildren = false;
@@ -411,7 +411,7 @@ public class FrameLayout extends ViewGroup {
gravity = DEFAULT_CHILD_GRAVITY;
}
- final int layoutDirection = getResolvedLayoutDirection();
+ final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
@@ -483,7 +483,7 @@ public class FrameLayout extends ViewGroup {
selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom);
}
- final int layoutDirection = getResolvedLayoutDirection();
+ final int layoutDirection = getLayoutDirection();
Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds,
layoutDirection);
@@ -603,6 +603,12 @@ public class FrameLayout extends ViewGroup {
*/
public int gravity = -1;
+ @Override
+ protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
+ width = a.getLayoutDimension(widthAttr, MATCH_PARENT);
+ height = a.getLayoutDimension(heightAttr, MATCH_PARENT);
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index b72b8cb..e0c5bbd 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -182,6 +182,12 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
*/
private boolean mIsRtl = true;
+ /**
+ * Offset between the center of the selected child view and the center of the Gallery.
+ * Used to reset position correctly during layout.
+ */
+ private int mSelectedCenterOffset;
+
public Gallery(Context context) {
this(context, null);
}
@@ -395,6 +401,14 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
setSelectionToCenterChild();
+ final View selChild = mSelectedChild;
+ if (selChild != null) {
+ final int childLeft = selChild.getLeft();
+ final int childCenter = selChild.getWidth() / 2;
+ final int galleryCenter = getWidth() / 2;
+ mSelectedCenterOffset = childLeft + childCenter - galleryCenter;
+ }
+
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
invalidate();
@@ -537,6 +551,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
// We haven't been callbacking during the fling, so do it now
super.selectionChanged();
}
+ mSelectedCenterOffset = 0;
invalidate();
}
@@ -650,7 +665,8 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
View sel = makeAndAddView(mSelectedPosition, 0, 0, true);
// Put the selected child in the center
- int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2);
+ int selectedOffset = childrenLeft + (childrenWidth / 2) - (sel.getWidth() / 2) +
+ mSelectedCenterOffset;
sel.offsetLeftAndRight(selectedOffset);
fillToGalleryRight();
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 37e0b90..63147dd 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -94,7 +94,7 @@ public class GridView extends AbsListView {
private View mReferenceView = null;
private View mReferenceViewInSelectedRow = null;
- private int mGravity = Gravity.LEFT;
+ private int mGravity = Gravity.START;
private final Rect mTempRect = new Rect();
@@ -300,9 +300,18 @@ public class GridView extends AbsListView {
final int columnWidth = mColumnWidth;
final int horizontalSpacing = mHorizontalSpacing;
+ final boolean isLayoutRtl = isLayoutRtl();
+
int last;
- int nextLeft = mListPadding.left +
- ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
+ int nextLeft;
+
+ if (isLayoutRtl) {
+ nextLeft = getWidth() - mListPadding.right - columnWidth -
+ ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
+ } else {
+ nextLeft = mListPadding.left +
+ ((mStretchMode == STRETCH_SPACING_UNIFORM) ? horizontalSpacing : 0);
+ }
if (!mStackFromBottom) {
last = Math.min(startPos + mNumColumns, mItemCount);
@@ -311,7 +320,8 @@ public class GridView extends AbsListView {
startPos = Math.max(0, startPos - mNumColumns + 1);
if (last - startPos < mNumColumns) {
- nextLeft += (mNumColumns - (last - startPos)) * (columnWidth + horizontalSpacing);
+ final int deltaLeft = (mNumColumns - (last - startPos)) * (columnWidth + horizontalSpacing);
+ nextLeft += (isLayoutRtl ? -1 : +1) * deltaLeft;
}
}
@@ -330,7 +340,7 @@ public class GridView extends AbsListView {
final int where = flow ? -1 : pos - startPos;
child = makeAndAddView(pos, y, flow, nextLeft, selected, where);
- nextLeft += columnWidth;
+ nextLeft += (isLayoutRtl ? -1 : +1) * columnWidth;
if (pos < last - 1) {
nextLeft += horizontalSpacing;
}
@@ -1415,7 +1425,7 @@ public class GridView extends AbsListView {
int childLeft;
final int childTop = flow ? y : y - h;
- final int layoutDirection = getResolvedLayoutDirection();
+ final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index cf28da4..87396fb 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -193,15 +193,6 @@ public class ImageView extends View {
}
}
- /**
- * @hide
- */
- @Override
- public int getResolvedLayoutDirection(Drawable dr) {
- return (dr == mDrawable) ?
- getResolvedLayoutDirection() : super.getResolvedLayoutDirection(dr);
- }
-
@Override
public boolean hasOverlappingRendering() {
return (getBackground() != null);
@@ -351,8 +342,15 @@ public class ImageView extends View {
updateDrawable(null);
mResource = resId;
mUri = null;
+
+ final int oldWidth = mDrawableWidth;
+ final int oldHeight = mDrawableHeight;
+
resolveUri();
- requestLayout();
+
+ if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
+ requestLayout();
+ }
invalidate();
}
}
@@ -376,8 +374,15 @@ public class ImageView extends View {
updateDrawable(null);
mResource = 0;
mUri = uri;
+
+ final int oldWidth = mDrawableWidth;
+ final int oldHeight = mDrawableHeight;
+
resolveUri();
- requestLayout();
+
+ if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
+ requestLayout();
+ }
invalidate();
}
}
@@ -392,8 +397,8 @@ public class ImageView extends View {
mResource = 0;
mUri = null;
- int oldWidth = mDrawableWidth;
- int oldHeight = mDrawableHeight;
+ final int oldWidth = mDrawableWidth;
+ final int oldHeight = mDrawableHeight;
updateDrawable(drawable);
@@ -675,6 +680,7 @@ public class ImageView extends View {
d.setState(getDrawableState());
}
d.setLevel(mLevel);
+ d.setLayoutDirection(getLayoutDirection());
mDrawableWidth = d.getIntrinsicWidth();
mDrawableHeight = d.getIntrinsicHeight();
applyColorMod();
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 09c0129..b6f0862 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -345,28 +345,42 @@ public class LinearLayout extends ViewGroup {
void drawDividersHorizontal(Canvas canvas) {
final int count = getVirtualChildCount();
+ final boolean isLayoutRtl = isLayoutRtl();
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child != null && child.getVisibility() != GONE) {
if (hasDividerBeforeChildAt(i)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final int left = child.getLeft() - lp.leftMargin - mDividerWidth;
- drawVerticalDivider(canvas, left);
+ final int position;
+ if (isLayoutRtl) {
+ position = child.getRight() + lp.rightMargin;
+ } else {
+ position = child.getLeft() - lp.leftMargin - mDividerWidth;
+ }
+ drawVerticalDivider(canvas, position);
}
}
}
if (hasDividerBeforeChildAt(count)) {
final View child = getVirtualChildAt(count - 1);
- int right = 0;
+ int position;
if (child == null) {
- right = getWidth() - getPaddingRight() - mDividerWidth;
+ if (isLayoutRtl) {
+ position = getPaddingLeft();
+ } else {
+ position = getWidth() - getPaddingRight() - mDividerWidth;
+ }
} else {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- right = child.getRight() + lp.rightMargin;
+ if (isLayoutRtl) {
+ position = child.getLeft() - lp.leftMargin - mDividerWidth;
+ } else {
+ position = child.getRight() + lp.rightMargin;
+ }
}
- drawVerticalDivider(canvas, right);
+ drawVerticalDivider(canvas, position);
}
}
@@ -1481,7 +1495,7 @@ public class LinearLayout extends ViewGroup {
if (gravity < 0) {
gravity = minorGravity;
}
- final int layoutDirection = getResolvedLayoutDirection();
+ final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
@@ -1545,7 +1559,7 @@ public class LinearLayout extends ViewGroup {
final int[] maxAscent = mMaxAscent;
final int[] maxDescent = mMaxDescent;
- final int layoutDirection = getResolvedLayoutDirection();
+ final int layoutDirection = getLayoutDirection();
switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) {
case Gravity.RIGHT:
// mTotalLength contains the padding already
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index 1d966b3..1c81d11 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -21,6 +21,7 @@ import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
@@ -31,6 +32,8 @@ import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewParent;
+import java.util.Locale;
+
/**
* A ListPopupWindow anchors itself to a host view and displays a
* list of choices.
@@ -92,6 +95,8 @@ public class ListPopupWindow {
private boolean mModal;
+ private int mLayoutDirection;
+
/**
* The provided prompt view should appear above list content.
*
@@ -193,6 +198,9 @@ public class ListPopupWindow {
mContext = context;
mPopup = new PopupWindow(context, attrs, defStyleAttr, defStyleRes);
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
+ // Set the default layout direction to match the default locale one
+ final Locale locale = mContext.getResources().getConfiguration().locale;
+ mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(locale);
}
/**
@@ -1013,6 +1021,8 @@ public class ListPopupWindow {
mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
}
+ mDropDownList.setLayoutDirection(mLayoutDirection);
+
dropDownView = mDropDownList;
View hintView = mPromptView;
@@ -1122,6 +1132,21 @@ public class ListPopupWindow {
}
/**
+ * Set the layout direction for this popup. Should be a resolved direction as the
+ * popup as no capacity to do the resolution on his own.
+ *
+ * @param layoutDirection One of {@link View#LAYOUT_DIRECTION_LTR},
+ * {@link View#LAYOUT_DIRECTION_RTL},
+ *
+ */
+ public void setLayoutDirection(int layoutDirection) {
+ mLayoutDirection = layoutDirection;
+ if (mDropDownList != null) {
+ mDropDownList.setLayoutDirection(mLayoutDirection);
+ }
+ }
+
+ /**
* <p>Wrapper class for a ListView. This wrapper can hijack the focus to
* make sure the list uses the appropriate drawables and states when
* displayed on screen within a drop down. The focus is never actually
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index e011c13..03507b5 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -29,6 +29,7 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
+import android.util.MathUtils;
import android.util.SparseBooleanArray;
import android.view.FocusFinder;
import android.view.KeyEvent;
@@ -37,6 +38,7 @@ import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
@@ -1490,6 +1492,10 @@ public class ListView extends AbsListView {
View focusLayoutRestoreView = null;
+ AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
+ View accessibilityFocusLayoutRestoreView = null;
+ int accessibilityFocusPosition = INVALID_POSITION;
+
// Remember stuff we will need down below
switch (mLayoutMode) {
case LAYOUT_SET_SELECTION:
@@ -1584,6 +1590,30 @@ public class ListView extends AbsListView {
requestFocus();
}
+ // Remember which child, if any, had accessibility focus.
+ final ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ final View accessFocusedView = viewRootImpl.getAccessibilityFocusedHost();
+ if (accessFocusedView != null) {
+ final View accessFocusedChild = findAccessibilityFocusedChild(
+ accessFocusedView);
+ if (accessFocusedChild != null) {
+ if (!dataChanged || isDirectChildHeaderOrFooter(accessFocusedChild)) {
+ // If the views won't be changing, try to maintain
+ // focus on the current view host and (if
+ // applicable) its virtual view.
+ accessibilityFocusLayoutRestoreView = accessFocusedView;
+ accessibilityFocusLayoutRestoreNode = viewRootImpl
+ .getAccessibilityFocusedVirtualView();
+ } else {
+ // Otherwise, try to maintain focus at the same
+ // position.
+ accessibilityFocusPosition = getPositionForView(accessFocusedChild);
+ }
+ }
+ }
+ }
+
// Clear out old views
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();
@@ -1682,6 +1712,22 @@ public class ListView extends AbsListView {
}
}
+ // Attempt to restore accessibility focus.
+ if (accessibilityFocusLayoutRestoreNode != null) {
+ accessibilityFocusLayoutRestoreNode.performAction(
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ } else if (accessibilityFocusLayoutRestoreView != null) {
+ accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
+ } else if (accessibilityFocusPosition != INVALID_POSITION) {
+ // Bound the position within the visible children.
+ final int position = MathUtils.constrain(
+ (accessibilityFocusPosition - mFirstPosition), 0, (getChildCount() - 1));
+ final View restoreView = getChildAt(position);
+ if (restoreView != null) {
+ restoreView.requestAccessibilityFocus();
+ }
+ }
+
// tell focus view we are done mucking with it, if it is still in
// our view hierarchy.
if (focusLayoutRestoreView != null
@@ -1713,6 +1759,22 @@ public class ListView extends AbsListView {
}
/**
+ * @param focusedView the view that has accessibility focus.
+ * @return the direct child that contains accessibility focus.
+ */
+ private View findAccessibilityFocusedChild(View focusedView) {
+ ViewParent viewParent = focusedView.getParent();
+ while ((viewParent instanceof View) && (viewParent != this)) {
+ focusedView = (View) viewParent;
+ viewParent = viewParent.getParent();
+ }
+ if (!(viewParent instanceof View)) {
+ return null;
+ }
+ return focusedView;
+ }
+
+ /**
* @param child a direct child of this list.
* @return Whether child is a header or footer view.
*/
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index a458f57..4918e48 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -51,10 +51,12 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.R;
+import libcore.icu.LocaleData;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
/**
* A widget that enables the user to select a number form a predefined range.
@@ -138,13 +140,6 @@ public class NumberPicker extends LinearLayout {
private static final int DEFAULT_LAYOUT_RESOURCE_ID = R.layout.number_picker;
/**
- * The numbers accepted by the input text's {@link Filter}
- */
- private static final char[] DIGIT_CHARACTERS = new char[] {
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
- };
-
- /**
* Constant for unspecified size.
*/
private static final int SIZE_UNSPECIFIED = -1;
@@ -154,23 +149,53 @@ public class NumberPicker extends LinearLayout {
* strings like "01". Keeping a static formatter etc. is the most efficient
* way to do this; it avoids creating temporary objects on every call to
* format().
- *
- * @hide
*/
- public static final NumberPicker.Formatter TWO_DIGIT_FORMATTER = new NumberPicker.Formatter() {
+ private static class TwoDigitFormatter implements NumberPicker.Formatter {
final StringBuilder mBuilder = new StringBuilder();
- final java.util.Formatter mFmt = new java.util.Formatter(mBuilder, java.util.Locale.US);
+ char mZeroDigit;
+ java.util.Formatter mFmt;
final Object[] mArgs = new Object[1];
+ TwoDigitFormatter() {
+ final Locale locale = Locale.getDefault();
+ init(locale);
+ }
+
+ private void init(Locale locale) {
+ mFmt = createFormatter(locale);
+ mZeroDigit = getZeroDigit(locale);
+ }
+
public String format(int value) {
+ final Locale currentLocale = Locale.getDefault();
+ if (mZeroDigit != getZeroDigit(currentLocale)) {
+ init(currentLocale);
+ }
mArgs[0] = value;
mBuilder.delete(0, mBuilder.length());
mFmt.format("%02d", mArgs);
return mFmt.toString();
}
- };
+
+ private static char getZeroDigit(Locale locale) {
+ return LocaleData.get(locale).zeroDigit;
+ }
+
+ private java.util.Formatter createFormatter(Locale locale) {
+ return new java.util.Formatter(mBuilder, locale);
+ }
+ }
+
+ private static final TwoDigitFormatter sTwoDigitFormatter = new TwoDigitFormatter();
+
+ /**
+ * @hide
+ */
+ public static final Formatter getTwoDigitFormatter() {
+ return sTwoDigitFormatter;
+ }
/**
* The increment button.
@@ -1156,7 +1181,7 @@ public class NumberPicker extends LinearLayout {
if (mDisplayedValues == null) {
float maxDigitWidth = 0;
for (int i = 0; i <= 9; i++) {
- final float digitWidth = mSelectorWheelPaint.measureText(String.valueOf(i));
+ final float digitWidth = mSelectorWheelPaint.measureText(formatNumberWithLocale(i));
if (digitWidth > maxDigitWidth) {
maxDigitWidth = digitWidth;
}
@@ -1336,6 +1361,14 @@ public class NumberPicker extends LinearLayout {
// Allow text entry rather than strictly numeric entry.
mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ // Make sure the min, max, respect the size of the displayed
+ // values. This will take care of the current value as well.
+ if (getMinValue() >= displayedValues.length) {
+ setMinValue(0);
+ }
+ if (getMaxValue() >= displayedValues.length) {
+ setMaxValue(displayedValues.length - 1);
+ }
} else {
mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
}
@@ -1417,19 +1450,6 @@ public class NumberPicker extends LinearLayout {
}
@Override
- public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
- // We do not want the real descendant to be considered focus search
- // since it is managed by the accessibility node provider.
- if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
- if (isAccessibilityFocusable()) {
- views.add(this);
- return;
- }
- }
- super.addFocusables(views, direction, focusableMode);
- }
-
- @Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(NumberPicker.class.getName());
@@ -1702,7 +1722,7 @@ public class NumberPicker extends LinearLayout {
}
private String formatNumber(int value) {
- return (mFormatter != null) ? mFormatter.format(value) : String.valueOf(value);
+ return (mFormatter != null) ? mFormatter.format(value) : formatNumberWithLocale(value);
}
private void validateInputTextView(View v) {
@@ -1862,6 +1882,20 @@ public class NumberPicker extends LinearLayout {
}
/**
+ * The numbers accepted by the input text's {@link Filter}
+ */
+ private static final char[] DIGIT_CHARACTERS = new char[] {
+ // Latin digits are the common case
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ // Arabic-Indic
+ '\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668'
+ , '\u0669',
+ // Extended Arabic-Indic
+ '\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4', '\u06f5', '\u06f6', '\u06f7', '\u06f8'
+ , '\u06f9'
+ };
+
+ /**
* Filter for accepting only valid indices or prefixes of the string
* representation of valid indices.
*/
@@ -2297,78 +2331,6 @@ public class NumberPicker extends LinearLayout {
return super.performAction(virtualViewId, action, arguments);
}
- @Override
- public AccessibilityNodeInfo findAccessibilityFocus(int virtualViewId) {
- return createAccessibilityNodeInfo(mAccessibilityFocusedView);
- }
-
- @Override
- public AccessibilityNodeInfo accessibilityFocusSearch(int direction, int virtualViewId) {
- switch (direction) {
- case View.ACCESSIBILITY_FOCUS_DOWN:
- case View.ACCESSIBILITY_FOCUS_FORWARD: {
- switch (mAccessibilityFocusedView) {
- case UNDEFINED: {
- return createAccessibilityNodeInfo(View.NO_ID);
- }
- case View.NO_ID: {
- if (hasVirtualDecrementButton()) {
- return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT);
- }
- }
- //$FALL-THROUGH$
- case VIRTUAL_VIEW_ID_DECREMENT: {
- return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT);
- }
- case VIRTUAL_VIEW_ID_INPUT: {
- if (hasVirtualIncrementButton()) {
- return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT);
- }
- }
- //$FALL-THROUGH$
- case VIRTUAL_VIEW_ID_INCREMENT: {
- View nextFocus = NumberPicker.this.focusSearch(direction);
- if (nextFocus != null) {
- return nextFocus.createAccessibilityNodeInfo();
- }
- return null;
- }
- }
- } break;
- case View.ACCESSIBILITY_FOCUS_UP:
- case View.ACCESSIBILITY_FOCUS_BACKWARD: {
- switch (mAccessibilityFocusedView) {
- case UNDEFINED: {
- return createAccessibilityNodeInfo(View.NO_ID);
- }
- case View.NO_ID: {
- if (hasVirtualIncrementButton()) {
- return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT);
- }
- }
- //$FALL-THROUGH$
- case VIRTUAL_VIEW_ID_INCREMENT: {
- return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT);
- }
- case VIRTUAL_VIEW_ID_INPUT: {
- if (hasVirtualDecrementButton()) {
- return createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT);
- }
- }
- //$FALL-THROUGH$
- case VIRTUAL_VIEW_ID_DECREMENT: {
- View nextFocus = NumberPicker.this.focusSearch(direction);
- if (nextFocus != null) {
- return nextFocus.createAccessibilityNodeInfo();
- }
- return null;
- }
- }
- } break;
- }
- return null;
- }
-
public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) {
switch (virtualViewId) {
case VIRTUAL_VIEW_ID_DECREMENT: {
@@ -2390,22 +2352,26 @@ public class NumberPicker extends LinearLayout {
}
private void sendAccessibilityEventForVirtualText(int eventType) {
- AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
- mInputText.onInitializeAccessibilityEvent(event);
- mInputText.onPopulateAccessibilityEvent(event);
- event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
- requestSendAccessibilityEvent(NumberPicker.this, event);
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ mInputText.onInitializeAccessibilityEvent(event);
+ mInputText.onPopulateAccessibilityEvent(event);
+ event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
+ requestSendAccessibilityEvent(NumberPicker.this, event);
+ }
}
private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType,
String text) {
- AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
- event.setClassName(Button.class.getName());
- event.setPackageName(mContext.getPackageName());
- event.getText().add(text);
- event.setEnabled(NumberPicker.this.isEnabled());
- event.setSource(NumberPicker.this, virtualViewId);
- requestSendAccessibilityEvent(NumberPicker.this, event);
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ event.setClassName(Button.class.getName());
+ event.setPackageName(mContext.getPackageName());
+ event.getText().add(text);
+ event.setEnabled(NumberPicker.this.isEnabled());
+ event.setSource(NumberPicker.this, virtualViewId);
+ requestSendAccessibilityEvent(NumberPicker.this, event);
+ }
}
private void findAccessibilityNodeInfosByTextInChild(String searchedLowerCase,
@@ -2506,14 +2472,22 @@ public class NumberPicker extends LinearLayout {
info.setParent((View) getParentForAccessibility());
info.setEnabled(NumberPicker.this.isEnabled());
info.setScrollable(true);
+
+ final float applicationScale =
+ getContext().getResources().getCompatibilityInfo().applicationScale;
+
Rect boundsInParent = mTempRect;
boundsInParent.set(left, top, right, bottom);
+ boundsInParent.scale(applicationScale);
info.setBoundsInParent(boundsInParent);
+
info.setVisibleToUser(isVisibleToUser());
+
Rect boundsInScreen = boundsInParent;
int[] locationOnScreen = mTempArray;
getLocationOnScreen(locationOnScreen);
boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
+ boundsInScreen.scale(applicationScale);
info.setBoundsInScreen(boundsInScreen);
if (mAccessibilityFocusedView != View.NO_ID) {
@@ -2566,4 +2540,8 @@ public class NumberPicker extends LinearLayout {
return null;
}
}
+
+ static private String formatNumberWithLocale(int value) {
+ return String.format(Locale.getDefault(), "%d", value);
+ }
}
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index 1c72a0d..f218199 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -72,10 +72,8 @@ public class OverScroller {
public OverScroller(Context context, Interpolator interpolator, boolean flywheel) {
mInterpolator = interpolator;
mFlywheel = flywheel;
- mScrollerX = new SplineOverScroller();
- mScrollerY = new SplineOverScroller();
-
- SplineOverScroller.initFromContext(context);
+ mScrollerX = new SplineOverScroller(context);
+ mScrollerY = new SplineOverScroller(context);
}
/**
@@ -585,8 +583,8 @@ public class OverScroller {
// Constant gravity value, used in the deceleration phase.
private static final float GRAVITY = 2000.0f;
- // A device specific coefficient adjusted to physical values.
- private static float PHYSICAL_COEF;
+ // A context-specific coefficient adjusted to physical values.
+ private float mPhysicalCoeff;
private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
@@ -636,20 +634,17 @@ public class OverScroller {
SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
}
- static void initFromContext(Context context) {
- final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
- PHYSICAL_COEF = SensorManager.GRAVITY_EARTH // g (m/s^2)
- * 39.37f // inch/meter
- * ppi
- * 0.84f; // look and feel tuning
- }
-
void setFriction(float friction) {
mFlingFriction = friction;
}
- SplineOverScroller() {
+ SplineOverScroller(Context context) {
mFinished = true;
+ final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
+ mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
+ * 39.37f // inch/meter
+ * ppi
+ * 0.84f; // look and feel tuning
}
void updateScroll(float q) {
@@ -785,13 +780,13 @@ public class OverScroller {
}
private double getSplineDeceleration(int velocity) {
- return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * PHYSICAL_COEF));
+ return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
}
private double getSplineFlingDistance(int velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
- return mFlingFriction * PHYSICAL_COEF * Math.exp(DECELERATION_RATE / decelMinusOne * l);
+ return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
}
/* Returns the duration, expressed in milliseconds */
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index f442912..af3365e 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -835,7 +835,7 @@ public class PopupWindow {
preparePopup(p);
if (gravity == Gravity.NO_GRAVITY) {
- gravity = Gravity.TOP | Gravity.LEFT;
+ gravity = Gravity.TOP | Gravity.START;
}
p.gravity = gravity;
p.x = x;
@@ -1003,7 +1003,7 @@ public class PopupWindow {
// screen. The view is then positioned to the appropriate location
// by setting the x and y offsets to match the anchor's bottom
// left corner
- p.gravity = Gravity.LEFT | Gravity.TOP;
+ p.gravity = Gravity.START | Gravity.TOP;
p.width = mLastWidth = mWidth;
p.height = mLastHeight = mHeight;
if (mBackground != null) {
@@ -1100,7 +1100,7 @@ public class PopupWindow {
boolean onTop = false;
- p.gravity = Gravity.LEFT | Gravity.TOP;
+ p.gravity = Gravity.START | Gravity.TOP;
anchor.getLocationOnScreen(mScreenLocation);
final Rect displayFrame = new Rect();
@@ -1134,7 +1134,7 @@ public class PopupWindow {
onTop = (displayFrame.bottom - mScreenLocation[1] - anchor.getHeight() - yoff) <
(mScreenLocation[1] - yoff - displayFrame.top);
if (onTop) {
- p.gravity = Gravity.LEFT | Gravity.BOTTOM;
+ p.gravity = Gravity.START | Gravity.BOTTOM;
p.y = root.getHeight() - mDrawingLocation[1] + yoff;
} else {
p.y = mDrawingLocation[1] + anchor.getHeight() + yoff;
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index f3f18d5..ea50e2e 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -478,6 +478,9 @@ public class ProgressBar extends View {
d.setCallback(this);
}
mIndeterminateDrawable = d;
+ if (mIndeterminateDrawable != null && canResolveLayoutDirection()) {
+ mIndeterminateDrawable.setLayoutDirection(getLayoutDirection());
+ }
if (mIndeterminate) {
mCurrentDrawable = d;
postInvalidate();
@@ -517,6 +520,9 @@ public class ProgressBar extends View {
if (d != null) {
d.setCallback(this);
+ if (canResolveLayoutDirection()) {
+ d.setLayoutDirection(getLayoutDirection());
+ }
// Make sure the ProgressBar is always tall enough
int drawableHeight = d.getMinimumHeight();
@@ -559,6 +565,23 @@ public class ProgressBar extends View {
if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
}
+ /**
+ * @hide
+ */
+ @Override
+ public void onResolveDrawables(int layoutDirection) {
+ final Drawable d = mCurrentDrawable;
+ if (d != null) {
+ d.setLayoutDirection(layoutDirection);
+ }
+ if (mIndeterminateDrawable != null) {
+ mIndeterminateDrawable.setLayoutDirection(layoutDirection);
+ }
+ if (mProgressDrawable != null) {
+ mProgressDrawable.setLayoutDirection(layoutDirection);
+ }
+ }
+
@Override
public void postInvalidate() {
if (!mNoInvalidate) {
@@ -648,6 +671,9 @@ public class ProgressBar extends View {
if (d instanceof LayerDrawable) {
progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
+ if (progressDrawable != null && canResolveLayoutDirection()) {
+ progressDrawable.setLayoutDirection(getLayoutDirection());
+ }
}
final int level = (int) (scale * MAX_LEVEL);
@@ -975,24 +1001,19 @@ public class ProgressBar extends View {
}
}
- /**
- * @hide
- */
- @Override
- public int getResolvedLayoutDirection(Drawable who) {
- return (who == mProgressDrawable || who == mIndeterminateDrawable) ?
- getResolvedLayoutDirection() : super.getResolvedLayoutDirection(who);
- }
-
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
updateDrawableBounds(w, h);
}
private void updateDrawableBounds(int w, int h) {
- // onDraw will translate the canvas so we draw starting at 0,0
- int right = w - mPaddingRight - mPaddingLeft;
- int bottom = h - mPaddingBottom - mPaddingTop;
+ // onDraw will translate the canvas so we draw starting at 0,0.
+ // Subtract out padding for the purposes of the calculations below.
+ w -= mPaddingRight + mPaddingLeft;
+ h -= mPaddingTop + mPaddingBottom;
+
+ int right = w;
+ int bottom = h;
int top = 0;
int left = 0;
@@ -1019,6 +1040,11 @@ public class ProgressBar extends View {
}
}
}
+ if (isLayoutRtl()) {
+ int tempLeft = left;
+ left = w - right;
+ right = w - tempLeft;
+ }
mIndeterminateDrawable.setBounds(left, top, right, bottom);
}
@@ -1036,7 +1062,12 @@ public class ProgressBar extends View {
// Translate canvas so a indeterminate circular progress bar with padding
// rotates properly in its animation
canvas.save();
- canvas.translate(mPaddingLeft, mPaddingTop);
+ if(isLayoutRtl()) {
+ canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
+ canvas.scale(-1.0f, 1.0f);
+ } else {
+ canvas.translate(mPaddingLeft, mPaddingTop);
+ }
long time = getDrawingTime();
if (mHasAnimation) {
mAnimation.getTransformation(time, mTransformation);
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index f217c9c..42d63b2 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -297,33 +297,6 @@ public class RadioGroup extends LinearLayout {
public LayoutParams(MarginLayoutParams source) {
super(source);
}
-
- /**
- * <p>Fixes the child's width to
- * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the child's
- * height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
- * when not specified in the XML file.</p>
- *
- * @param a the styled attributes set
- * @param widthAttr the width attribute to fetch
- * @param heightAttr the height attribute to fetch
- */
- @Override
- protected void setBaseAttributes(TypedArray a,
- int widthAttr, int heightAttr) {
-
- if (a.hasValue(widthAttr)) {
- width = a.getLayoutDimension(widthAttr, "layout_width");
- } else {
- width = WRAP_CONTENT;
- }
-
- if (a.hasValue(heightAttr)) {
- height = a.getLayoutDimension(heightAttr, "layout_height");
- } else {
- height = WRAP_CONTENT;
- }
- }
}
/**
@@ -376,7 +349,7 @@ public class RadioGroup extends LinearLayout {
int id = child.getId();
// generates an id if it's missing
if (id == View.NO_ID) {
- id = child.hashCode();
+ id = View.generateViewId();
child.setId(id);
}
((RadioButton) child).setOnCheckedChangeWidgetListener(
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 569cf99..455355f 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -149,8 +149,34 @@ public class RelativeLayout extends ViewGroup {
* bounds of its RelativeLayout parent.
*/
public static final int CENTER_VERTICAL = 15;
+ /**
+ * Rule that aligns a child's end edge with another child's start edge.
+ */
+ public static final int START_OF = 16;
+ /**
+ * Rule that aligns a child's start edge with another child's end edge.
+ */
+ public static final int END_OF = 17;
+ /**
+ * Rule that aligns a child's start edge with another child's start edge.
+ */
+ public static final int ALIGN_START = 18;
+ /**
+ * Rule that aligns a child's end edge with another child's end edge.
+ */
+ public static final int ALIGN_END = 19;
+ /**
+ * Rule that aligns the child's start edge with its RelativeLayout
+ * parent's start edge.
+ */
+ public static final int ALIGN_PARENT_START = 20;
+ /**
+ * Rule that aligns the child's end edge with its RelativeLayout
+ * parent's end edge.
+ */
+ public static final int ALIGN_PARENT_END = 21;
- private static final int VERB_COUNT = 16;
+ private static final int VERB_COUNT = 22;
private static final int[] RULES_VERTICAL = {
@@ -158,13 +184,13 @@ public class RelativeLayout extends ViewGroup {
};
private static final int[] RULES_HORIZONTAL = {
- LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT
+ LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END
};
private View mBaselineView = null;
private boolean mHasBaselineAlignedChild;
- private int mGravity = Gravity.LEFT | Gravity.TOP;
+ private int mGravity = Gravity.START | Gravity.TOP;
private final Rect mContentBounds = new Rect();
private final Rect mSelfBounds = new Rect();
private int mIgnoreGravity;
@@ -204,7 +230,7 @@ public class RelativeLayout extends ViewGroup {
/**
* Defines which View is ignored when the gravity is applied. This setting has no
- * effect if the gravity is <code>Gravity.LEFT | Gravity.TOP</code>.
+ * effect if the gravity is <code>Gravity.START | Gravity.TOP</code>.
*
* @param viewId The id of the View to be ignored by gravity, or 0 if no View
* should be ignored.
@@ -234,7 +260,7 @@ public class RelativeLayout extends ViewGroup {
/**
* Describes how the child views are positioned. Defaults to
- * <code>Gravity.LEFT | Gravity.TOP</code>.
+ * <code>Gravity.START | Gravity.TOP</code>.
*
* <p>Note that since RelativeLayout considers the positioning of each child
* relative to one another to be significant, setting gravity will affect
@@ -369,7 +395,7 @@ public class RelativeLayout extends ViewGroup {
View ignore = null;
int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
- final boolean horizontalGravity = gravity != Gravity.LEFT && gravity != 0;
+ final boolean horizontalGravity = gravity != Gravity.START && gravity != 0;
gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final boolean verticalGravity = gravity != Gravity.TOP && gravity != 0;
@@ -457,6 +483,8 @@ public class RelativeLayout extends ViewGroup {
}
}
+ final int layoutDirection = getLayoutDirection();
+
if (isWrapContentWidth) {
// Width already has left padding in it since it was calculated by looking at
// the right of each child view
@@ -474,7 +502,7 @@ public class RelativeLayout extends ViewGroup {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
- final int[] rules = params.getRules();
+ final int[] rules = params.getRules(layoutDirection);
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
centerHorizontal(child, params, width);
} else if (rules[ALIGN_PARENT_RIGHT] != 0) {
@@ -504,7 +532,7 @@ public class RelativeLayout extends ViewGroup {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
- final int[] rules = params.getRules();
+ final int[] rules = params.getRules(layoutDirection);
if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
centerVertical(child, params, height);
} else if (rules[ALIGN_PARENT_BOTTOM] != 0) {
@@ -523,7 +551,6 @@ public class RelativeLayout extends ViewGroup {
height - mPaddingBottom);
final Rect contentBounds = mContentBounds;
- final int layoutDirection = getResolvedLayoutDirection();
Gravity.apply(mGravity, right - left, bottom - top, selfBounds, contentBounds,
layoutDirection);
@@ -551,7 +578,8 @@ public class RelativeLayout extends ViewGroup {
}
private void alignBaseline(View child, LayoutParams params) {
- int[] rules = params.getRules();
+ final int layoutDirection = getLayoutDirection();
+ int[] rules = params.getRules(layoutDirection);
int anchorBaseline = getRelatedViewBaseline(rules, ALIGN_BASELINE);
if (anchorBaseline != -1) {
@@ -619,7 +647,7 @@ public class RelativeLayout extends ViewGroup {
/**
* Get a measure spec that accounts for all of the constraints on this view.
- * This includes size contstraints imposed by the RelativeLayout as well as
+ * This includes size constraints imposed by the RelativeLayout as well as
* the View's desired dimension.
*
* @param childStart The left or top field of the child's layout params
@@ -672,7 +700,7 @@ public class RelativeLayout extends ViewGroup {
childSpecSize = childSize;
}
} else if (childSize == LayoutParams.MATCH_PARENT) {
- // Child wanted to be as big as possible. Give all availble
+ // Child wanted to be as big as possible. Give all available
// space
childSpecMode = MeasureSpec.EXACTLY;
childSpecSize = maxAvailable;
@@ -681,7 +709,7 @@ public class RelativeLayout extends ViewGroup {
// to communicate available space if we know
// our max size
if (maxAvailable >= 0) {
- // We have a maxmum size in this dimension.
+ // We have a maximum size in this dimension.
childSpecMode = MeasureSpec.AT_MOST;
childSpecSize = maxAvailable;
} else {
@@ -699,7 +727,9 @@ public class RelativeLayout extends ViewGroup {
private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth,
boolean wrapContent) {
- int[] rules = params.getRules();
+ final int layoutDirection = getLayoutDirection();
+ int[] rules = params.getRules(layoutDirection);
+ params.onResolveLayoutDirection(layoutDirection);
if (params.mLeft < 0 && params.mRight >= 0) {
// Right is fixed, but left varies
@@ -718,11 +748,18 @@ public class RelativeLayout extends ViewGroup {
}
return true;
} else {
- params.mLeft = mPaddingLeft + params.leftMargin;
- params.mRight = params.mLeft + child.getMeasuredWidth();
+ // This is the default case. For RTL we start from the right and for LTR we start
+ // from the left. This will give LEFT/TOP for LTR and RIGHT/TOP for RTL.
+ if (isLayoutRtl()) {
+ params.mRight = myWidth - mPaddingRight- params.rightMargin;
+ params.mLeft = params.mRight - child.getMeasuredWidth();
+ } else {
+ params.mLeft = mPaddingLeft + params.leftMargin;
+ params.mRight = params.mLeft + child.getMeasuredWidth();
+ }
}
}
- return rules[ALIGN_PARENT_RIGHT] != 0;
+ return rules[ALIGN_PARENT_END] != 0;
}
private boolean positionChildVertical(View child, LayoutParams params, int myHeight,
@@ -755,7 +792,8 @@ public class RelativeLayout extends ViewGroup {
}
private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) {
- int[] rules = childParams.getRules();
+ final int layoutDirection = getLayoutDirection();
+ int[] rules = childParams.getRules(layoutDirection);
RelativeLayout.LayoutParams anchorParams;
// -1 indicated a "soft requirement" in that direction. For example:
@@ -945,8 +983,8 @@ public class RelativeLayout extends ViewGroup {
if (child.getVisibility() != GONE) {
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
+ st.onResolveLayoutDirection(getLayoutDirection());
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
-
}
}
}
@@ -1061,6 +1099,12 @@ public class RelativeLayout extends ViewGroup {
* @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerInParent
* @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerHorizontal
* @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical
+ * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toStartOf
+ * @attr ref android.R.styleable#RelativeLayout_Layout_layout_toEndOf
+ * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignStart
+ * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignEnd
+ * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentStart
+ * @attr ref android.R.styleable#RelativeLayout_Layout_layout_alignParentEnd
*/
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
@ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = {
@@ -1079,15 +1123,28 @@ public class RelativeLayout extends ViewGroup {
@ViewDebug.IntToString(from = CENTER_IN_PARENT, to = "center"),
@ViewDebug.IntToString(from = CENTER_VERTICAL, to = "centerVertical"),
@ViewDebug.IntToString(from = LEFT_OF, to = "leftOf"),
- @ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf")
+ @ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf"),
+ @ViewDebug.IntToString(from = ALIGN_START, to = "alignStart"),
+ @ViewDebug.IntToString(from = ALIGN_END, to = "alignEnd"),
+ @ViewDebug.IntToString(from = ALIGN_PARENT_START, to = "alignParentStart"),
+ @ViewDebug.IntToString(from = ALIGN_PARENT_END, to = "alignParentEnd"),
+ @ViewDebug.IntToString(from = START_OF, to = "startOf"),
+ @ViewDebug.IntToString(from = END_OF, to = "endOf")
}, mapping = {
@ViewDebug.IntToString(from = TRUE, to = "true"),
@ViewDebug.IntToString(from = 0, to = "false/NO_ID")
})
+
private int[] mRules = new int[VERB_COUNT];
+ private int[] mInitialRules = new int[VERB_COUNT];
private int mLeft, mTop, mRight, mBottom;
+ private int mStart = DEFAULT_RELATIVE;
+ private int mEnd = DEFAULT_RELATIVE;
+
+ private boolean mRulesChanged = false;
+
/**
* When true, uses the parent as the anchor if the anchor doesn't exist or if
* the anchor's visibility is GONE.
@@ -1102,6 +1159,7 @@ public class RelativeLayout extends ViewGroup {
com.android.internal.R.styleable.RelativeLayout_Layout);
final int[] rules = mRules;
+ final int[] initialRules = mInitialRules;
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
@@ -1158,9 +1216,31 @@ public class RelativeLayout extends ViewGroup {
case com.android.internal.R.styleable.RelativeLayout_Layout_layout_centerVertical:
rules[CENTER_VERTICAL] = a.getBoolean(attr, false) ? TRUE : 0;
break;
+ case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toStartOf:
+ rules[START_OF] = a.getResourceId(attr, 0);
+ break;
+ case com.android.internal.R.styleable.RelativeLayout_Layout_layout_toEndOf:
+ rules[END_OF] = a.getResourceId(attr, 0);
+ break;
+ case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignStart:
+ rules[ALIGN_START] = a.getResourceId(attr, 0);
+ break;
+ case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignEnd:
+ rules[ALIGN_END] = a.getResourceId(attr, 0);
+ break;
+ case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentStart:
+ rules[ALIGN_PARENT_START] = a.getBoolean(attr, false) ? TRUE : 0;
+ break;
+ case com.android.internal.R.styleable.RelativeLayout_Layout_layout_alignParentEnd:
+ rules[ALIGN_PARENT_END] = a.getBoolean(attr, false) ? TRUE : 0;
+ break;
}
}
+ for (int n = LEFT_OF; n < VERB_COUNT; n++) {
+ initialRules[n] = rules[n];
+ }
+
a.recycle();
}
@@ -1192,7 +1272,7 @@ public class RelativeLayout extends ViewGroup {
* Adds a layout rule to be interpreted by the RelativeLayout. This
* method should only be used for constraints that don't refer to another sibling
* (e.g., CENTER_IN_PARENT) or take a boolean value ({@link RelativeLayout#TRUE}
- * for true or - for false). To specify a verb that takes a subject, use
+ * for true or 0 for false). To specify a verb that takes a subject, use
* {@link #addRule(int, int)} instead.
*
* @param verb One of the verbs defined by
@@ -1202,6 +1282,8 @@ public class RelativeLayout extends ViewGroup {
*/
public void addRule(int verb) {
mRules[verb] = TRUE;
+ mInitialRules[verb] = TRUE;
+ mRulesChanged = true;
}
/**
@@ -1220,12 +1302,88 @@ public class RelativeLayout extends ViewGroup {
*/
public void addRule(int verb, int anchor) {
mRules[verb] = anchor;
+ mInitialRules[verb] = anchor;
+ mRulesChanged = true;
+ }
+
+ /**
+ * Removes a layout rule to be interpreted by the RelativeLayout.
+ *
+ * @param verb One of the verbs defined by
+ * {@link android.widget.RelativeLayout RelativeLayout}, such as
+ * ALIGN_WITH_PARENT_LEFT.
+ * @see #addRule(int)
+ * @see #addRule(int, int)
+ */
+ public void removeRule(int verb) {
+ mRules[verb] = 0;
+ mInitialRules[verb] = 0;
+ mRulesChanged = true;
+ }
+
+ private boolean hasRelativeRules() {
+ return (mInitialRules[START_OF] != 0 || mInitialRules[END_OF] != 0 ||
+ mInitialRules[ALIGN_START] != 0 || mInitialRules[ALIGN_END] != 0 ||
+ mInitialRules[ALIGN_PARENT_START] != 0 || mInitialRules[ALIGN_PARENT_END] != 0);
+ }
+
+ private void resolveRules(int layoutDirection) {
+ final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL);
+ // Reset to initial state
+ for (int n = LEFT_OF; n < VERB_COUNT; n++) {
+ mRules[n] = mInitialRules[n];
+ }
+ // Apply rules depending on direction
+ if (mRules[ALIGN_START] != 0) {
+ mRules[isLayoutRtl ? ALIGN_RIGHT : ALIGN_LEFT] = mRules[ALIGN_START];
+ }
+ if (mRules[ALIGN_END] != 0) {
+ mRules[isLayoutRtl ? ALIGN_LEFT : ALIGN_RIGHT] = mRules[ALIGN_END];
+ }
+ if (mRules[START_OF] != 0) {
+ mRules[isLayoutRtl ? RIGHT_OF : LEFT_OF] = mRules[START_OF];
+ }
+ if (mRules[END_OF] != 0) {
+ mRules[isLayoutRtl ? LEFT_OF : RIGHT_OF] = mRules[END_OF];
+ }
+ if (mRules[ALIGN_PARENT_START] != 0) {
+ mRules[isLayoutRtl ? ALIGN_PARENT_RIGHT : ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START];
+ }
+ if (mRules[ALIGN_PARENT_END] != 0) {
+ mRules[isLayoutRtl ? ALIGN_PARENT_LEFT : ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END];
+ }
+ mRulesChanged = false;
}
/**
* Retrieves a complete list of all supported rules, where the index is the rule
* verb, and the element value is the value specified, or "false" if it was never
- * set.
+ * set. If there are relative rules defined (*_START / *_END), they will be resolved
+ * depending on the layout direction.
+ *
+ * @param layoutDirection the direction of the layout.
+ * Should be either {@link View#LAYOUT_DIRECTION_LTR}
+ * or {@link View#LAYOUT_DIRECTION_RTL}
+ * @return the supported rules
+ * @see #addRule(int, int)
+ *
+ * @hide
+ */
+ public int[] getRules(int layoutDirection) {
+ if (hasRelativeRules() &&
+ (mRulesChanged || layoutDirection != getLayoutDirection())) {
+ resolveRules(layoutDirection);
+ if (layoutDirection != getLayoutDirection()) {
+ setLayoutDirection(layoutDirection);
+ }
+ }
+ return mRules;
+ }
+
+ /**
+ * Retrieves a complete list of all supported rules, where the index is the rule
+ * verb, and the element value is the value specified, or "false" if it was never
+ * set. There will be no resolution of relative rules done.
*
* @return the supported rules
* @see #addRule(int, int)
@@ -1233,6 +1391,24 @@ public class RelativeLayout extends ViewGroup {
public int[] getRules() {
return mRules;
}
+
+ @Override
+ public void onResolveLayoutDirection(int layoutDirection) {
+ final boolean isLayoutRtl = isLayoutRtl();
+ if (isLayoutRtl) {
+ if (mStart != DEFAULT_RELATIVE) mRight = mStart;
+ if (mEnd != DEFAULT_RELATIVE) mLeft = mEnd;
+ } else {
+ if (mStart != DEFAULT_RELATIVE) mLeft = mStart;
+ if (mEnd != DEFAULT_RELATIVE) mRight = mEnd;
+ }
+
+ if (hasRelativeRules() && layoutDirection != getLayoutDirection()) {
+ resolveRules(layoutDirection);
+ }
+ // This will set the layout direction
+ super.onResolveLayoutDirection(layoutDirection);
+ }
}
private static class DependencyGraph {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 4710798..8d83774 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -23,7 +23,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -35,9 +34,9 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
-import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.LayoutInflater.Filter;
import android.view.RemotableViewMethod;
@@ -52,6 +51,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.HashMap;
/**
@@ -61,9 +61,9 @@ import java.util.ArrayList;
* the content of the inflated hierarchy.
*/
public class RemoteViews implements Parcelable, Filter {
-
+
private static final String LOG_TAG = "RemoteViews";
-
+
/**
* The intent extra that contains the appWidgetId.
* @hide
@@ -71,11 +71,18 @@ public class RemoteViews implements Parcelable, Filter {
static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId";
/**
- * The package name of the package containing the layout
+ * User that these views should be applied as. Requires
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} when
+ * crossing user boundaries.
+ */
+ private UserHandle mUser = android.os.Process.myUserHandle();
+
+ /**
+ * The package name of the package containing the layout
* resource. (Added to the parcel)
*/
private final String mPackage;
-
+
/**
* The resource ID of the layout file. (Added to the parcel)
*/
@@ -86,7 +93,7 @@ public class RemoteViews implements Parcelable, Filter {
* inflated
*/
private ArrayList<Action> mActions;
-
+
/**
* A class to keep track of memory usage by this RemoteViews
*/
@@ -187,6 +194,10 @@ public class RemoteViews implements Parcelable, Filter {
public abstract void apply(View root, ViewGroup rootParent,
OnClickHandler handler) throws ActionException;
+ public static final int MERGE_REPLACE = 0;
+ public static final int MERGE_APPEND = 1;
+ public static final int MERGE_IGNORE = 2;
+
public int describeContents() {
return 0;
}
@@ -203,6 +214,67 @@ public class RemoteViews implements Parcelable, Filter {
public void setBitmapCache(BitmapCache bitmapCache) {
// Do nothing
}
+
+ public int mergeBehavior() {
+ return MERGE_REPLACE;
+ }
+
+ public abstract String getActionName();
+
+ public String getUniqueKey() {
+ return (getActionName() + viewId);
+ }
+
+ int viewId;
+ }
+
+ /**
+ * Merges the passed RemoteViews actions with this RemoteViews actions according to
+ * action-specific merge rules.
+ *
+ * @param newRv
+ *
+ * @hide
+ */
+ public void mergeRemoteViews(RemoteViews newRv) {
+ if (newRv == null) return;
+ // We first copy the new RemoteViews, as the process of merging modifies the way the actions
+ // reference the bitmap cache. We don't want to modify the object as it may need to
+ // be merged and applied multiple times.
+ RemoteViews copy = newRv.clone();
+
+ HashMap<String, Action> map = new HashMap<String, Action>();
+ if (mActions == null) {
+ mActions = new ArrayList<Action>();
+ }
+
+ int count = mActions.size();
+ for (int i = 0; i < count; i++) {
+ Action a = mActions.get(i);
+ map.put(a.getUniqueKey(), a);
+ }
+
+ ArrayList<Action> newActions = copy.mActions;
+ if (newActions == null) return;
+ count = newActions.size();
+ for (int i = 0; i < count; i++) {
+ Action a = newActions.get(i);
+ String key = newActions.get(i).getUniqueKey();
+ int mergeBehavior = newActions.get(i).mergeBehavior();
+ if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) {
+ mActions.remove(map.get(key));
+ map.remove(key);
+ }
+
+ // If the merge behavior is ignore, we don't bother keeping the extra action
+ if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) {
+ mActions.add(a);
+ }
+ }
+
+ // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache
+ mBitmapCache = new BitmapCache();
+ setBitmapCache(mBitmapCache);
}
private class SetEmptyView extends Action {
@@ -239,6 +311,10 @@ public class RemoteViews implements Parcelable, Filter {
adapterView.setEmptyView(emptyView);
}
+
+ public String getActionName() {
+ return "SetEmptyView";
+ }
}
private class SetOnClickFillInIntent extends Action {
@@ -316,7 +392,10 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- int viewId;
+ public String getActionName() {
+ return "SetOnClickFillInIntent";
+ }
+
Intent fillInIntent;
public final static int TAG = 9;
@@ -399,7 +478,10 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- int viewId;
+ public String getActionName() {
+ return "SetPendingIntentTemplate";
+ }
+
PendingIntent pendingIntentTemplate;
public final static int TAG = 8;
@@ -447,13 +529,18 @@ public class RemoteViews implements Parcelable, Filter {
if (target instanceof AbsListView) {
AbsListView v = (AbsListView) target;
v.setRemoteViewsAdapter(intent);
+ v.setRemoteViewsOnClickHandler(handler);
} else if (target instanceof AdapterViewAnimator) {
AdapterViewAnimator v = (AdapterViewAnimator) target;
v.setRemoteViewsAdapter(intent);
+ v.setRemoteViewsOnClickHandler(handler);
}
}
- int viewId;
+ public String getActionName() {
+ return "SetRemoteViewsAdapterIntent";
+ }
+
Intent intent;
public final static int TAG = 10;
@@ -522,13 +609,13 @@ public class RemoteViews implements Parcelable, Filter {
.getCompatibilityInfo().applicationScale;
final int[] pos = new int[2];
v.getLocationOnScreen(pos);
-
+
final Rect rect = new Rect();
rect.left = (int) (pos[0] * appScale + 0.5f);
rect.top = (int) (pos[1] * appScale + 0.5f);
rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
-
+
final Intent intent = new Intent();
intent.setSourceBounds(rect);
handler.onClickHandler(v, pendingIntent, intent);
@@ -539,7 +626,10 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- int viewId;
+ public String getActionName() {
+ return "SetOnClickPendingIntent";
+ }
+
PendingIntent pendingIntent;
public final static int TAG = 1;
@@ -567,7 +657,7 @@ public class RemoteViews implements Parcelable, Filter {
this.filterMode = mode;
this.level = level;
}
-
+
public SetDrawableParameters(Parcel parcel) {
viewId = parcel.readInt();
targetBackground = parcel.readInt() != 0;
@@ -581,7 +671,7 @@ public class RemoteViews implements Parcelable, Filter {
}
level = parcel.readInt();
}
-
+
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(TAG);
dest.writeInt(viewId);
@@ -596,12 +686,12 @@ public class RemoteViews implements Parcelable, Filter {
}
dest.writeInt(level);
}
-
+
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final View target = root.findViewById(viewId);
if (target == null) return;
-
+
// Pick the correct drawable to modify for this view
Drawable targetDrawable = null;
if (targetBackground) {
@@ -610,7 +700,7 @@ public class RemoteViews implements Parcelable, Filter {
ImageView imageView = (ImageView) target;
targetDrawable = imageView.getDrawable();
}
-
+
if (targetDrawable != null) {
// Perform modifications only if values are set correctly
if (alpha != -1) {
@@ -625,7 +715,10 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- int viewId;
+ public String getActionName() {
+ return "SetDrawableParameters";
+ }
+
boolean targetBackground;
int alpha;
int colorFilter;
@@ -634,9 +727,8 @@ public class RemoteViews implements Parcelable, Filter {
public final static int TAG = 3;
}
-
+
private class ReflectionActionWithoutParams extends Action {
- int viewId;
String methodName;
public final static int TAG = 5;
@@ -688,6 +780,19 @@ public class RemoteViews implements Parcelable, Filter {
throw new ActionException(ex);
}
}
+
+ public int mergeBehavior() {
+ // we don't need to build up showNext or showPrevious calls
+ if (methodName.equals("showNext") || methodName.equals("showPrevious")) {
+ return MERGE_IGNORE;
+ } else {
+ return MERGE_REPLACE;
+ }
+ }
+
+ public String getActionName() {
+ return "ReflectionActionWithoutParams";
+ }
}
private static class BitmapCache {
@@ -755,7 +860,6 @@ public class RemoteViews implements Parcelable, Filter {
private class BitmapReflectionAction extends Action {
int bitmapId;
- int viewId;
Bitmap bitmap;
String methodName;
@@ -794,6 +898,10 @@ public class RemoteViews implements Parcelable, Filter {
bitmapId = bitmapCache.getBitmapId(bitmap);
}
+ public String getActionName() {
+ return "BitmapReflectionAction";
+ }
+
public final static int TAG = 12;
}
@@ -814,11 +922,12 @@ public class RemoteViews implements Parcelable, Filter {
static final int STRING = 9;
static final int CHAR_SEQUENCE = 10;
static final int URI = 11;
+ // BITMAP actions are never stored in the list of actions. They are only used locally
+ // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache.
static final int BITMAP = 12;
static final int BUNDLE = 13;
static final int INTENT = 14;
- int viewId;
String methodName;
int type;
Object value;
@@ -938,7 +1047,7 @@ public class RemoteViews implements Parcelable, Filter {
out.writeString((String)this.value);
break;
case CHAR_SEQUENCE:
- TextUtils.writeToParcel((CharSequence)this.value, out, flags);
+ TextUtils.writeToParcel((CharSequence)this.value, out, flags);
break;
case URI:
out.writeInt(this.value != null ? 1 : 0);
@@ -1041,20 +1150,20 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- @Override
- public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
- // We currently only calculate Bitmap memory usage
- switch (this.type) {
- case BITMAP:
- if (this.value != null) {
- final Bitmap b = (Bitmap) this.value;
- counter.addBitmapMemory(b);
- }
- break;
- default:
- break;
+ public int mergeBehavior() {
+ // smoothScrollBy is cumulative, everything else overwites.
+ if (methodName.equals("smoothScrollBy")) {
+ return MERGE_APPEND;
+ } else {
+ return MERGE_REPLACE;
}
}
+
+ public String getActionName() {
+ // Each type of reflection action corresponds to a setter, so each should be seen as
+ // unique from the standpoint of merging.
+ return "ReflectionAction" + this.methodName + this.type;
+ }
}
private void configureRemoteViewsAsChild(RemoteViews rv) {
@@ -1131,7 +1240,14 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- int viewId;
+ public String getActionName() {
+ return "ViewGroupAction" + this.nestedViews == null ? "Remove" : "Add";
+ }
+
+ public int mergeBehavior() {
+ return MERGE_APPEND;
+ }
+
RemoteViews nestedViews;
public final static int TAG = 4;
@@ -1182,7 +1298,10 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- int viewId;
+ public String getActionName() {
+ return "TextViewDrawableAction";
+ }
+
boolean isRelative = false;
int d1, d2, d3, d4;
@@ -1220,7 +1339,10 @@ public class RemoteViews implements Parcelable, Filter {
target.setTextSize(units, size);
}
- int viewId;
+ public String getActionName() {
+ return "TextViewSizeAction";
+ }
+
int units;
float size;
@@ -1264,7 +1386,10 @@ public class RemoteViews implements Parcelable, Filter {
target.setPadding(left, top, right, bottom);
}
- int viewId;
+ public String getActionName() {
+ return "ViewPaddingAction";
+ }
+
int left, top, right, bottom;
public final static int TAG = 14;
@@ -1314,7 +1439,7 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Create a new RemoteViews object that will display the views contained
* in the specified layout file.
- *
+ *
* @param packageName Name of the package that contains the layout resource
* @param layoutId The id of the layout resource
*/
@@ -1328,11 +1453,16 @@ public class RemoteViews implements Parcelable, Filter {
recalculateMemoryUsage();
}
+ /** {@hide} */
+ public void setUser(UserHandle user) {
+ mUser = user;
+ }
+
private boolean hasLandscapeAndPortraitLayouts() {
return (mLandscape != null) && (mPortrait != null);
}
- /**
+ /**
* Create a new RemoteViews object that will inflate as the specified
* landspace or portrait RemoteViews, depending on the current configuration.
*
@@ -1364,7 +1494,7 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Reads a RemoteViews object from a parcel.
- *
+ *
* @param parcel
*/
public RemoteViews(Parcel parcel) {
@@ -1450,23 +1580,12 @@ public class RemoteViews implements Parcelable, Filter {
recalculateMemoryUsage();
}
- @Override
- public RemoteViews clone() {
- RemoteViews that;
- if (!hasLandscapeAndPortraitLayouts()) {
- that = new RemoteViews(mPackage, mLayoutId);
- if (mActions != null) {
- that.mActions = (ArrayList<Action>)mActions.clone();
- }
- } else {
- RemoteViews land = mLandscape.clone();
- RemoteViews port = mPortrait.clone();
- that = new RemoteViews(land, port);
- }
- // update the memory usage stats of the cloned RemoteViews
- that.recalculateMemoryUsage();
- return that;
+ public RemoteViews clone() {
+ Parcel p = Parcel.obtain();
+ writeToParcel(p, 0);
+ p.setDataPosition(0);
+ return new RemoteViews(p);
}
public String getPackage() {
@@ -1547,7 +1666,7 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Add an action to be executed on the remote side when apply is called.
- *
+ *
* @param a The action to add
*/
private void addAction(Action a) {
@@ -1619,7 +1738,7 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Equivalent to calling View.setVisibility
- *
+ *
* @param viewId The id of the view whose visibility should change
* @param visibility The new visibility for the view
*/
@@ -1629,7 +1748,7 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Equivalent to calling TextView.setText
- *
+ *
* @param viewId The id of the view whose text should change
* @param text The new text for the view
*/
@@ -1639,7 +1758,7 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Equivalent to calling {@link TextView#setTextSize(int, float)}
- *
+ *
* @param viewId The id of the view whose text size should change
* @param units The units of size (e.g. COMPLEX_UNIT_SP)
* @param size The size of the text
@@ -1649,20 +1768,23 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
- * Equivalent to calling
+ * Equivalent to calling
* {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}.
*
* @param viewId The id of the view whose text should change
* @param left The id of a drawable to place to the left of the text, or 0
* @param top The id of a drawable to place above the text, or 0
* @param right The id of a drawable to place to the right of the text, or 0
- * @param bottom The id of a drawable to place below the text, or 0
+ * @param bottom The id of a drawable to place below the text, or 0
*/
public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) {
addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
}
/**
+ * Equivalent to calling {@link
+ * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}.
+ *
* @param viewId The id of the view whose text should change
* @param start The id of a drawable to place before the text (relative to the
* layout direction), or 0
@@ -1676,17 +1798,17 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Equivalent to calling ImageView.setImageResource
- *
+ *
* @param viewId The id of the view whose drawable should change
* @param srcId The new resource id for the drawable
*/
- public void setImageViewResource(int viewId, int srcId) {
+ public void setImageViewResource(int viewId, int srcId) {
setInt(viewId, "setImageResource", srcId);
}
/**
* Equivalent to calling ImageView.setImageURI
- *
+ *
* @param viewId The id of the view whose drawable should change
* @param uri The Uri for the image
*/
@@ -1696,7 +1818,7 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Equivalent to calling ImageView.setImageBitmap
- *
+ *
* @param viewId The id of the view whose bitmap should change
* @param bitmap The new Bitmap for the drawable
*/
@@ -1719,7 +1841,7 @@ public class RemoteViews implements Parcelable, Filter {
* {@link Chronometer#setFormat Chronometer.setFormat},
* and {@link Chronometer#start Chronometer.start()} or
* {@link Chronometer#stop Chronometer.stop()}.
- *
+ *
* @param viewId The id of the {@link Chronometer} to change
* @param base The time at which the timer would have read 0:00. This
* time should be based off of
@@ -1733,21 +1855,21 @@ public class RemoteViews implements Parcelable, Filter {
setString(viewId, "setFormat", format);
setBoolean(viewId, "setStarted", started);
}
-
+
/**
* Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
* {@link ProgressBar#setProgress ProgressBar.setProgress}, and
* {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
*
* If indeterminate is true, then the values for max and progress are ignored.
- *
+ *
* @param viewId The id of the {@link ProgressBar} to change
* @param max The 100% value for the progress bar
* @param progress The current value of the progress bar.
- * @param indeterminate True if the progress bar is indeterminate,
+ * @param indeterminate True if the progress bar is indeterminate,
* false if not.
*/
- public void setProgressBar(int viewId, int max, int progress,
+ public void setProgressBar(int viewId, int max, int progress,
boolean indeterminate) {
setBoolean(viewId, "setIndeterminate", indeterminate);
if (!indeterminate) {
@@ -1755,12 +1877,12 @@ public class RemoteViews implements Parcelable, Filter {
setInt(viewId, "setProgress", progress);
}
}
-
+
/**
* Equivalent to calling
* {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
* to launch the provided {@link PendingIntent}.
- *
+ *
* When setting the on-click action of items within collections (eg. {@link ListView},
* {@link StackView} etc.), this method will not work. Instead, use {@link
* RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with
@@ -1820,7 +1942,7 @@ public class RemoteViews implements Parcelable, Filter {
* view.
* <p>
* You can omit specific calls by marking their values with null or -1.
- *
+ *
* @param viewId The id of the view that contains the target
* {@link Drawable}
* @param targetBackground If true, apply these parameters to the
@@ -1846,7 +1968,7 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
- *
+ *
* @param viewId The id of the view whose text color should change
* @param color Sets the text color for all the states (normal, selected,
* focused) to be this color.
@@ -2034,6 +2156,8 @@ public class RemoteViews implements Parcelable, Filter {
* @param value The value to pass to the method.
*/
public void setUri(int viewId, String methodName, Uri value) {
+ // Resolve any filesystem path before sending remotely
+ value = value.getCanonicalUri();
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
}
@@ -2074,15 +2198,25 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
- * Equivalent to calling View.setContentDescription
+ * Equivalent to calling View.setContentDescription(CharSequence).
*
- * @param viewId The id of the view whose content description should change
- * @param contentDescription The new content description for the view
+ * @param viewId The id of the view whose content description should change.
+ * @param contentDescription The new content description for the view.
*/
public void setContentDescription(int viewId, CharSequence contentDescription) {
setCharSequence(viewId, "setContentDescription", contentDescription);
}
+ /**
+ * Equivalent to calling View.setLabelFor(int).
+ *
+ * @param viewId The id of the view whose property to set.
+ * @param labeledId The id of a view for which this view serves as a label.
+ */
+ public void setLabelFor(int viewId, int labeledId) {
+ setInt(viewId, "setLabelFor", labeledId);
+ }
+
private RemoteViews getRemoteViewsToApply(Context context) {
if (hasLandscapeAndPortraitLayouts()) {
int orientation = context.getResources().getConfiguration().orientation;
@@ -2098,16 +2232,16 @@ public class RemoteViews implements Parcelable, Filter {
/**
* Inflates the view hierarchy represented by this object and applies
* all of the actions.
- *
+ *
* <p><strong>Caller beware: this may throw</strong>
- *
+ *
* @param context Default context to use
* @param parent Parent that the resulting view hierarchy will be attached to. This method
* does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
* @return The inflated view hierarchy
*/
public View apply(Context context, ViewGroup parent) {
- return apply(context, parent, DEFAULT_ON_CLICK_HANDLER);
+ return apply(context, parent, null);
}
/** @hide */
@@ -2135,12 +2269,12 @@ public class RemoteViews implements Parcelable, Filter {
* Applies all of the actions to the provided view.
*
* <p><strong>Caller beware: this may throw</strong>
- *
+ *
* @param v The view to apply the actions to. This should be the result of
* the {@link #apply(Context,ViewGroup)} call.
*/
public void reapply(Context context, View v) {
- reapply(context, v, DEFAULT_ON_CLICK_HANDLER);
+ reapply(context, v, null);
}
/** @hide */
@@ -2163,6 +2297,7 @@ public class RemoteViews implements Parcelable, Filter {
private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
if (mActions != null) {
+ handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
final int count = mActions.size();
for (int i = 0; i < count; i++) {
Action a = mActions.get(i);
@@ -2177,7 +2312,8 @@ public class RemoteViews implements Parcelable, Filter {
if (packageName != null) {
try {
- c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED);
+ c = context.createPackageContextAsUser(
+ packageName, Context.CONTEXT_RESTRICTED, mUser);
} catch (NameNotFoundException e) {
Log.e(LOG_TAG, "Package name " + packageName + " not found");
c = context;
@@ -2191,7 +2327,7 @@ public class RemoteViews implements Parcelable, Filter {
/* (non-Javadoc)
* Used to restrict the views which can be inflated
- *
+ *
* @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
*/
public boolean onLoadClass(Class clazz) {
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index f0109ce..e481702 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -36,6 +36,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
+import android.widget.RemoteViews.OnClickHandler;
import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
@@ -68,6 +69,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
private LayoutInflater mLayoutInflater;
private RemoteViewsAdapterServiceConnection mServiceConnection;
private WeakReference<RemoteAdapterConnectionCallback> mCallback;
+ private OnClickHandler mRemoteViewsOnClickHandler;
private FixedSizeRemoteViewsCache mCache;
private int mVisibleWindowLowerBound;
private int mVisibleWindowUpperBound;
@@ -277,11 +279,11 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
* @param view the RemoteViews that was loaded. If null, the RemoteViews was not loaded
* successfully.
*/
- public void onRemoteViewsLoaded(RemoteViews view) {
+ public void onRemoteViewsLoaded(RemoteViews view, OnClickHandler handler) {
try {
// Remove all the children of this layout first
removeAllViews();
- addView(view.apply(getContext(), this));
+ addView(view.apply(getContext(), this, handler));
} catch (Exception e) {
Log.e(TAG, "Failed to apply RemoteViews.");
}
@@ -330,7 +332,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// Notify all the references for that position of the newly loaded RemoteViews
final LinkedList<RemoteViewsFrameLayout> refs = mReferences.get(pos);
for (final RemoteViewsFrameLayout ref : refs) {
- ref.onRemoteViewsLoaded(view);
+ ref.onRemoteViewsLoaded(view, mRemoteViewsOnClickHandler);
}
refs.clear();
@@ -421,7 +423,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
private RemoteViewsFrameLayout createLoadingView(int position, View convertView,
- ViewGroup parent, Object lock, LayoutInflater layoutInflater) {
+ ViewGroup parent, Object lock, LayoutInflater layoutInflater, OnClickHandler
+ handler) {
// Create and return a new FrameLayout, and setup the references for this position
final Context context = parent.getContext();
RemoteViewsFrameLayout layout = new RemoteViewsFrameLayout(context);
@@ -433,7 +436,8 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
if (mUserLoadingView != null) {
// Try to inflate user-specified loading view
try {
- View loadingView = mUserLoadingView.apply(parent.getContext(), parent);
+ View loadingView = mUserLoadingView.apply(parent.getContext(), parent,
+ handler);
loadingView.setTagInternal(com.android.internal.R.id.rowTypeId,
new Integer(0));
layout.addView(loadingView);
@@ -448,7 +452,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// Use the size of the first row as a guide for the size of the loading view
if (mFirstViewHeight < 0) {
try {
- View firstView = mFirstView.apply(parent.getContext(), parent);
+ View firstView = mFirstView.apply(parent.getContext(), parent, handler);
firstView.measure(
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
@@ -815,6 +819,10 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
return mDataReady;
}
+ public void setRemoteViewsOnClickHandler(OnClickHandler handler) {
+ mRemoteViewsOnClickHandler = handler;
+ }
+
public void saveRemoteViewsCache() {
final Pair<Intent.FilterComparison, Integer> key = new Pair<Intent.FilterComparison,
Integer> (new Intent.FilterComparison(mIntent), mAppWidgetId);
@@ -1102,7 +1110,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
// Reuse the convert view where possible
if (layout != null) {
if (convertViewTypeId == typeId) {
- rv.reapply(context, convertViewChild);
+ rv.reapply(context, convertViewChild, mRemoteViewsOnClickHandler);
return layout;
}
layout.removeAllViews();
@@ -1111,7 +1119,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
}
// Otherwise, create a new view to be returned
- View newView = rv.apply(context, parent);
+ View newView = rv.apply(context, parent, mRemoteViewsOnClickHandler);
newView.setTagInternal(com.android.internal.R.id.rowTypeId,
new Integer(typeId));
layout.addView(newView);
@@ -1127,7 +1135,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
loadingView = metaData.createLoadingView(position, convertView, parent,
- mCache, mLayoutInflater);
+ mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
}
return loadingView;
} finally {
@@ -1140,7 +1148,7 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
final RemoteViewsMetaData metaData = mCache.getMetaData();
synchronized (metaData) {
loadingView = metaData.createLoadingView(position, convertView, parent,
- mCache, mLayoutInflater);
+ mCache, mLayoutInflater, mRemoteViewsOnClickHandler);
}
mRequestedViews.add(position, loadingView);
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 8747dc3..bc41931 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -463,6 +463,13 @@ public class ScrollView extends FrameLayout {
return true;
}
+ /*
+ * Don't try to intercept touch if we can't scroll anyway.
+ */
+ if (getScrollY() == 0 && !canScrollVertically(1)) {
+ return false;
+ }
+
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
/*
diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java
index a6e83f0..3a28e75 100644
--- a/core/java/android/widget/Scroller.java
+++ b/core/java/android/widget/Scroller.java
@@ -57,45 +57,70 @@ public class Scroller {
private boolean mFlywheel;
private float mVelocity;
+ private float mCurrVelocity;
+ private int mDistance;
+
+ private float mFlingFriction = ViewConfiguration.getScrollFriction();
private static final int DEFAULT_DURATION = 250;
private static final int SCROLL_MODE = 0;
private static final int FLING_MODE = 1;
- private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
- private static float ALPHA = 800; // pixels / seconds
- private static float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
- private static float END_TENSION = 1.0f - START_TENSION;
+ private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
+ private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)
+ private static final float START_TENSION = 0.5f;
+ private static final float END_TENSION = 1.0f;
+ private static final float P1 = START_TENSION * INFLEXION;
+ private static final float P2 = 1.0f - END_TENSION * (1.0f - INFLEXION);
+
private static final int NB_SAMPLES = 100;
- private static final float[] SPLINE = new float[NB_SAMPLES + 1];
+ private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
+ private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
private float mDeceleration;
private final float mPpi;
+ // A context-specific coefficient adjusted to physical values.
+ private float mPhysicalCoeff;
+
static {
float x_min = 0.0f;
- for (int i = 0; i <= NB_SAMPLES; i++) {
- final float t = (float) i / NB_SAMPLES;
+ float y_min = 0.0f;
+ for (int i = 0; i < NB_SAMPLES; i++) {
+ final float alpha = (float) i / NB_SAMPLES;
+
float x_max = 1.0f;
float x, tx, coef;
while (true) {
x = x_min + (x_max - x_min) / 2.0f;
coef = 3.0f * x * (1.0f - x);
- tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
- if (Math.abs(tx - t) < 1E-5) break;
- if (tx > t) x_max = x;
+ tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
+ if (Math.abs(tx - alpha) < 1E-5) break;
+ if (tx > alpha) x_max = x;
else x_min = x;
}
- final float d = coef + x * x * x;
- SPLINE[i] = d;
+ SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;
+
+ float y_max = 1.0f;
+ float y, dy;
+ while (true) {
+ y = y_min + (y_max - y_min) / 2.0f;
+ coef = 3.0f * y * (1.0f - y);
+ dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
+ if (Math.abs(dy - alpha) < 1E-5) break;
+ if (dy > alpha) y_max = y;
+ else y_min = y;
+ }
+ SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
}
- SPLINE[NB_SAMPLES] = 1.0f;
+ SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
// This controls the viscous fluid effect (how much of it)
sViscousFluidScale = 8.0f;
// must be set to 1.0 (used in viscousFluid())
sViscousFluidNormalize = 1.0f;
sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
+
}
private static float sViscousFluidScale;
@@ -129,6 +154,8 @@ public class Scroller {
mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
mFlywheel = flywheel;
+
+ mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
}
/**
@@ -140,6 +167,7 @@ public class Scroller {
*/
public final void setFriction(float friction) {
mDeceleration = computeDeceleration(friction);
+ mFlingFriction = friction;
}
private float computeDeceleration(float friction) {
@@ -202,7 +230,8 @@ public class Scroller {
* negative.
*/
public float getCurrVelocity() {
- return mVelocity - mDeceleration * timePassed() / 2000.0f;
+ return mMode == FLING_MODE ?
+ mCurrVelocity : mVelocity - mDeceleration * timePassed() / 2000.0f;
}
/**
@@ -269,11 +298,18 @@ public class Scroller {
case FLING_MODE:
final float t = (float) timePassed / mDuration;
final int index = (int) (NB_SAMPLES * t);
- final float t_inf = (float) index / NB_SAMPLES;
- final float t_sup = (float) (index + 1) / NB_SAMPLES;
- final float d_inf = SPLINE[index];
- final float d_sup = SPLINE[index + 1];
- final float distanceCoef = d_inf + (t - t_inf) / (t_sup - t_inf) * (d_sup - d_inf);
+ float distanceCoef = 1.f;
+ float velocityCoef = 0.f;
+ if (index < NB_SAMPLES) {
+ final float t_inf = (float) index / NB_SAMPLES;
+ final float t_sup = (float) (index + 1) / NB_SAMPLES;
+ final float d_inf = SPLINE_POSITION[index];
+ final float d_sup = SPLINE_POSITION[index + 1];
+ velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
+ distanceCoef = d_inf + (t - t_inf) * velocityCoef;
+ }
+
+ mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
// Pin to mMinX <= mCurrX <= mMaxX
@@ -392,8 +428,7 @@ public class Scroller {
float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
mVelocity = velocity;
- final double l = Math.log(START_TENSION * velocity / ALPHA);
- mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
+ mDuration = getSplineFlingDuration(velocity);
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
@@ -401,25 +436,41 @@ public class Scroller {
float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
- int totalDistance =
- (int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
+ double totalDistance = getSplineFlingDistance(velocity);
+ mDistance = (int) (totalDistance * Math.signum(velocity));
mMinX = minX;
mMaxX = maxX;
mMinY = minY;
mMaxY = maxY;
- mFinalX = startX + Math.round(totalDistance * coeffX);
+ mFinalX = startX + (int) Math.round(totalDistance * coeffX);
// Pin to mMinX <= mFinalX <= mMaxX
mFinalX = Math.min(mFinalX, mMaxX);
mFinalX = Math.max(mFinalX, mMinX);
- mFinalY = startY + Math.round(totalDistance * coeffY);
+ mFinalY = startY + (int) Math.round(totalDistance * coeffY);
// Pin to mMinY <= mFinalY <= mMaxY
mFinalY = Math.min(mFinalY, mMaxY);
mFinalY = Math.max(mFinalY, mMinY);
}
+ private double getSplineDeceleration(float velocity) {
+ return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
+ }
+
+ private int getSplineFlingDuration(float velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return (int) (1000.0 * Math.exp(l / decelMinusOne));
+ }
+
+ private double getSplineFlingDistance(float velocity) {
+ final double l = getSplineDeceleration(velocity);
+ final double decelMinusOne = DECELERATION_RATE - 1.0;
+ return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
+ }
+
static float viscousFluid(float x)
{
x *= sViscousFluidScale;
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 86433d4..cd8638d 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -326,7 +326,6 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
int oldLeft, int oldTop, int oldRight, int oldBottom) {
adjustDropDownSizeAndPosition();
}
-
});
}
@@ -1285,15 +1284,22 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
Resources res = getContext().getResources();
int anchorPadding = mSearchPlate.getPaddingLeft();
Rect dropDownPadding = new Rect();
+ final boolean isLayoutRtl = isLayoutRtl();
int iconOffset = mIconifiedByDefault
? res.getDimensionPixelSize(R.dimen.dropdownitem_icon_width)
+ res.getDimensionPixelSize(R.dimen.dropdownitem_text_padding_left)
: 0;
mQueryTextView.getDropDownBackground().getPadding(dropDownPadding);
- mQueryTextView.setDropDownHorizontalOffset(-(dropDownPadding.left + iconOffset)
- + anchorPadding);
- mQueryTextView.setDropDownWidth(mDropDownAnchor.getWidth() + dropDownPadding.left
- + dropDownPadding.right + iconOffset - (anchorPadding));
+ int offset;
+ if (isLayoutRtl) {
+ offset = - dropDownPadding.left;
+ } else {
+ offset = anchorPadding - (dropDownPadding.left + iconOffset);
+ }
+ mQueryTextView.setDropDownHorizontalOffset(offset);
+ final int width = mDropDownAnchor.getWidth() + dropDownPadding.left
+ + dropDownPadding.right + iconOffset - anchorPadding;
+ mQueryTextView.setDropDownWidth(width);
}
}
@@ -1347,6 +1353,11 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
};
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ mQueryTextView.setLayoutDirection(layoutDirection);
+ }
+
/**
* Query rewriting.
*/
diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java
index 14edd10..517246b 100644
--- a/core/java/android/widget/SlidingDrawer.java
+++ b/core/java/android/widget/SlidingDrawer.java
@@ -78,7 +78,12 @@ import android.view.accessibility.AccessibilityNodeInfo;
* @attr ref android.R.styleable#SlidingDrawer_orientation
* @attr ref android.R.styleable#SlidingDrawer_allowSingleTap
* @attr ref android.R.styleable#SlidingDrawer_animateOnClick
+ *
+ * @deprecated This class is not supported anymore. It is recommended you
+ * base your own implementation on the source code for the Android Open
+ * Source Project if you must use it in your application.
*/
+@Deprecated
public class SlidingDrawer extends ViewGroup {
public static final int ORIENTATION_HORIZONTAL = 0;
public static final int ORIENTATION_VERTICAL = 1;
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 64834b2..317baf1 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -355,7 +355,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
public void setGravity(int gravity) {
if (mGravity != gravity) {
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) {
- gravity |= Gravity.LEFT;
+ gravity |= Gravity.START;
}
mGravity = gravity;
requestLayout();
@@ -460,7 +460,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
/**
* Creates and positions all views for this Spinner.
*
- * @param delta Change in the selected position. +1 moves selection is moving to the right,
+ * @param delta Change in the selected position. +1 means selection is moving to the right,
* so views are scrolling to the left. -1 means selection is moving to the left.
*/
@Override
@@ -492,7 +492,9 @@ public class Spinner extends AbsSpinner implements OnClickListener {
View sel = makeAndAddView(mSelectedPosition);
int width = sel.getMeasuredWidth();
int selectedOffset = childrenLeft;
- switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ final int layoutDirection = getLayoutDirection();
+ final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
+ switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
selectedOffset = childrenLeft + (childrenWidth / 2) - (width / 2);
break;
@@ -939,19 +941,18 @@ public class Spinner extends AbsSpinner implements OnClickListener {
@Override
public void show() {
final Drawable background = getBackground();
- int bgOffset = 0;
+ int hOffset = 0;
if (background != null) {
background.getPadding(mTempRect);
- bgOffset = -mTempRect.left;
+ hOffset = isLayoutRtl() ? mTempRect.right : -mTempRect.left;
} else {
mTempRect.left = mTempRect.right = 0;
}
final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
+ final int spinnerPaddingRight = Spinner.this.getPaddingRight();
+ final int spinnerWidth = Spinner.this.getWidth();
if (mDropDownWidth == WRAP_CONTENT) {
- final int spinnerWidth = Spinner.this.getWidth();
- final int spinnerPaddingRight = Spinner.this.getPaddingRight();
-
int contentWidth = measureContentWidth(
(SpinnerAdapter) mAdapter, getBackground());
final int contentWidthLimit = mContext.getResources()
@@ -959,17 +960,20 @@ public class Spinner extends AbsSpinner implements OnClickListener {
if (contentWidth > contentWidthLimit) {
contentWidth = contentWidthLimit;
}
-
setContentWidth(Math.max(
contentWidth, spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight));
} else if (mDropDownWidth == MATCH_PARENT) {
- final int spinnerWidth = Spinner.this.getWidth();
- final int spinnerPaddingRight = Spinner.this.getPaddingRight();
setContentWidth(spinnerWidth - spinnerPaddingLeft - spinnerPaddingRight);
} else {
setContentWidth(mDropDownWidth);
}
- setHorizontalOffset(bgOffset + spinnerPaddingLeft);
+
+ if (isLayoutRtl()) {
+ hOffset += spinnerWidth - spinnerPaddingRight - getWidth();
+ } else {
+ hOffset += spinnerPaddingLeft;
+ }
+ setHorizontalOffset(hOffset);
setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
super.show();
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 293eda1..6853660 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -1412,8 +1412,8 @@ public class StackView extends AdapterViewAnimator {
return null;
}
- Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
- Bitmap.Config.ARGB_8888);
+ Bitmap bitmap = Bitmap.createBitmap(v.getResources().getDisplayMetrics(),
+ v.getMeasuredWidth(), v.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(bitmap);
float rotationX = v.getRotationX();
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index cea613f..e754c17 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -480,12 +480,6 @@ public class Switch extends CompoundButton {
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
-
if (mOnLayout == null) {
mOnLayout = makeLayout(mTextOn);
}
@@ -501,34 +495,6 @@ public class Switch extends CompoundButton {
mThumbWidth = maxTextWidth + mThumbTextPadding * 2;
- switch (widthMode) {
- case MeasureSpec.AT_MOST:
- widthSize = Math.min(widthSize, switchWidth);
- break;
-
- case MeasureSpec.UNSPECIFIED:
- widthSize = switchWidth;
- break;
-
- case MeasureSpec.EXACTLY:
- // Just use what we were given
- break;
- }
-
- switch (heightMode) {
- case MeasureSpec.AT_MOST:
- heightSize = Math.min(heightSize, switchHeight);
- break;
-
- case MeasureSpec.UNSPECIFIED:
- heightSize = switchHeight;
- break;
-
- case MeasureSpec.EXACTLY:
- // Just use what we were given
- break;
- }
-
mSwitchWidth = switchWidth;
mSwitchHeight = switchHeight;
@@ -542,9 +508,9 @@ public class Switch extends CompoundButton {
@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
- CharSequence text = isChecked() ? mOnLayout.getText() : mOffLayout.getText();
- if (!TextUtils.isEmpty(text)) {
- event.getText().add(text);
+ Layout layout = isChecked() ? mOnLayout : mOffLayout;
+ if (layout != null && !TextUtils.isEmpty(layout.getText())) {
+ event.getText().add(layout.getText());
}
}
@@ -662,7 +628,7 @@ public class Switch extends CompoundButton {
mVelocityTracker.computeCurrentVelocity(1000);
float xvel = mVelocityTracker.getXVelocity();
if (Math.abs(xvel) > mMinFlingVelocity) {
- newState = xvel > 0;
+ newState = isLayoutRtl() ? (xvel < 0) : (xvel > 0);
} else {
newState = getTargetCheckedState();
}
@@ -680,13 +646,25 @@ public class Switch extends CompoundButton {
}
private boolean getTargetCheckedState() {
- return mThumbPosition >= getThumbScrollRange() / 2;
+ if (isLayoutRtl()) {
+ return mThumbPosition <= getThumbScrollRange() / 2;
+ } else {
+ return mThumbPosition >= getThumbScrollRange() / 2;
+ }
+ }
+
+ private void setThumbPosition(boolean checked) {
+ if (isLayoutRtl()) {
+ mThumbPosition = checked ? 0 : getThumbScrollRange();
+ } else {
+ mThumbPosition = checked ? getThumbScrollRange() : 0;
+ }
}
@Override
public void setChecked(boolean checked) {
super.setChecked(checked);
- mThumbPosition = isChecked() ? getThumbScrollRange() : 0;
+ setThumbPosition(isChecked());
invalidate();
}
@@ -694,10 +672,19 @@ public class Switch extends CompoundButton {
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- mThumbPosition = isChecked() ? getThumbScrollRange() : 0;
+ setThumbPosition(isChecked());
+
+ int switchRight;
+ int switchLeft;
+
+ if (isLayoutRtl()) {
+ switchLeft = getPaddingLeft();
+ switchRight = switchLeft + mSwitchWidth;
+ } else {
+ switchRight = getWidth() - getPaddingRight();
+ switchLeft = switchRight - mSwitchWidth;
+ }
- int switchRight = getWidth() - getPaddingRight();
- int switchLeft = switchRight - mSwitchWidth;
int switchTop = 0;
int switchBottom = 0;
switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) {
@@ -763,16 +750,32 @@ public class Switch extends CompoundButton {
mTextPaint.drawableState = getDrawableState();
Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
-
- canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2,
- (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
- switchText.draw(canvas);
+ if (switchText != null) {
+ canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2,
+ (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
+ switchText.draw(canvas);
+ }
canvas.restore();
}
@Override
+ public int getCompoundPaddingLeft() {
+ if (!isLayoutRtl()) {
+ return super.getCompoundPaddingLeft();
+ }
+ int padding = super.getCompoundPaddingLeft() + mSwitchWidth;
+ if (!TextUtils.isEmpty(getText())) {
+ padding += mSwitchPadding;
+ }
+ return padding;
+ }
+
+ @Override
public int getCompoundPaddingRight() {
+ if (isLayoutRtl()) {
+ return super.getCompoundPaddingRight();
+ }
int padding = super.getCompoundPaddingRight() + mSwitchWidth;
if (!TextUtils.isEmpty(getText())) {
padding += mSwitchPadding;
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index 78e9453..113299a 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -184,6 +184,10 @@ public class TableLayout extends LinearLayout {
mShrinkableColumns = new SparseBooleanArray();
}
+ // TableLayouts are always in vertical orientation; keep this tracked
+ // for shared LinearLayout code.
+ setOrientation(VERTICAL);
+
mPassThroughListener = new PassThroughHierarchyChangeListener();
// make sure to call the parent class method to avoid potential
// infinite loops
@@ -739,11 +743,7 @@ public class TableLayout extends LinearLayout {
@Override
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
this.width = MATCH_PARENT;
- if (a.hasValue(heightAttr)) {
- this.height = a.getLayoutDimension(heightAttr, "layout_height");
- } else {
- this.height = WRAP_CONTENT;
- }
+ this.height = a.getLayoutDimension(heightAttr, WRAP_CONTENT);
}
}
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index 01c4c2c..db3853f 100644
--- a/core/java/android/widget/TableRow.java
+++ b/core/java/android/widget/TableRow.java
@@ -226,7 +226,7 @@ public class TableRow extends LinearLayout {
final int childWidth = child.getMeasuredWidth();
lp.mOffset[LayoutParams.LOCATION_NEXT] = columnWidth - childWidth;
- final int layoutDirection = getResolvedLayoutDirection();
+ final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
@@ -502,19 +502,8 @@ public class TableRow extends LinearLayout {
@Override
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
- // We don't want to force users to specify a layout_width
- if (a.hasValue(widthAttr)) {
- width = a.getLayoutDimension(widthAttr, "layout_width");
- } else {
- width = MATCH_PARENT;
- }
-
- // We don't want to force users to specify a layout_height
- if (a.hasValue(heightAttr)) {
- height = a.getLayoutDimension(heightAttr, "layout_height");
- } else {
- height = WRAP_CONTENT;
- }
+ width = a.getLayoutDimension(widthAttr, MATCH_PARENT);
+ height = a.getLayoutDimension(heightAttr, WRAP_CONTENT);
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 01617da..410a0ca 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -302,6 +302,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// The alignment to pass to Layout, or null if not resolved.
private Layout.Alignment mLayoutAlignment;
+ private int mResolvedTextAlignment;
private boolean mResolvedDrawables;
@@ -1542,11 +1543,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Returns the start padding of the view, plus space for the start
* Drawable if any.
- * @hide
*/
public int getCompoundPaddingStart() {
resolveDrawables();
- switch(getResolvedLayoutDirection()) {
+ switch(getLayoutDirection()) {
default:
case LAYOUT_DIRECTION_LTR:
return getCompoundPaddingLeft();
@@ -1558,11 +1558,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Returns the end padding of the view, plus space for the end
* Drawable if any.
- * @hide
*/
public int getCompoundPaddingEnd() {
resolveDrawables();
- switch(getResolvedLayoutDirection()) {
+ switch(getLayoutDirection()) {
default:
case LAYOUT_DIRECTION_LTR:
return getCompoundPaddingRight();
@@ -1656,7 +1655,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Returns the total start padding of the view, including the start
* Drawable if any.
- * @hide
*/
public int getTotalPaddingStart() {
return getCompoundPaddingStart();
@@ -1665,7 +1663,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Returns the total end padding of the view, including the end
* Drawable if any.
- * @hide
*/
public int getTotalPaddingEnd() {
return getCompoundPaddingEnd();
@@ -1868,7 +1865,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_drawableTop
* @attr ref android.R.styleable#TextView_drawableEnd
* @attr ref android.R.styleable#TextView_drawableBottom
- * @hide
*/
public void setCompoundDrawablesRelative(Drawable start, Drawable top,
Drawable end, Drawable bottom) {
@@ -1990,7 +1986,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_drawableTop
* @attr ref android.R.styleable#TextView_drawableEnd
* @attr ref android.R.styleable#TextView_drawableBottom
- * @hide
*/
@android.view.RemotableViewMethod
public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
@@ -2014,7 +2009,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_drawableTop
* @attr ref android.R.styleable#TextView_drawableEnd
* @attr ref android.R.styleable#TextView_drawableBottom
- * @hide
*/
public void setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top,
Drawable end, Drawable bottom) {
@@ -2061,7 +2055,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_drawableTop
* @attr ref android.R.styleable#TextView_drawableEnd
* @attr ref android.R.styleable#TextView_drawableBottom
- * @hide
*/
public Drawable[] getCompoundDrawablesRelative() {
final Drawables dr = mDrawables;
@@ -2211,6 +2204,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Get the default {@link Locale} of the text in this TextView.
+ * @return the default {@link Locale} of the text in this TextView.
+ */
+ public Locale getTextLocale() {
+ return mTextPaint.getTextLocale();
+ }
+
+ /**
+ * Set the default {@link Locale} of the text in this TextView to the given value. This value
+ * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK
+ * locales to disambiguate Hanzi/Kanji/Hanja characters.
+ *
+ * @param locale the {@link Locale} for drawing text, must not be null.
+ *
+ * @see Paint#setTextLocale
+ */
+ public void setTextLocale(Locale locale) {
+ mTextPaint.setTextLocale(locale);
+ }
+
+ /**
* @return the size (in pixels) of the default text size in this TextView.
*/
@ViewDebug.ExportedProperty(category = "text")
@@ -4449,9 +4463,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mTemporaryDetach = false;
- // Resolve drawables as the layout direction has been resolved
- resolveDrawables();
-
if (mEditor != null) mEditor.onAttachedToWindow();
}
@@ -4586,23 +4597,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- /**
- * @hide
- */
- @Override
- public int getResolvedLayoutDirection(Drawable who) {
- if (who == null) return View.LAYOUT_DIRECTION_LTR;
- if (mDrawables != null) {
- final Drawables drawables = mDrawables;
- if (who == drawables.mDrawableLeft || who == drawables.mDrawableRight ||
- who == drawables.mDrawableTop || who == drawables.mDrawableBottom ||
- who == drawables.mDrawableStart || who == drawables.mDrawableEnd) {
- return getResolvedLayoutDirection();
- }
- }
- return super.getResolvedLayoutDirection(who);
- }
-
@Override
public boolean hasOverlappingRendering() {
return (getBackground() != null || mText instanceof Spannable || hasSelection());
@@ -4862,18 +4856,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
- final int layoutDirection = getResolvedLayoutDirection();
+ final boolean isLayoutRtl = isLayoutRtl();
+
+ final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
(absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
- canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft -
- getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f);
+ final int width = mRight - mLeft;
+ final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
+ final float dx = mLayout.getLineRight(0) - (width - padding);
+ canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
}
if (mMarquee != null && mMarquee.isRunning()) {
- canvas.translate(-mMarquee.mScroll, 0.0f);
+ final float dx = -mMarquee.getScroll();
+ canvas.translate(isLayoutRtl ? -dx : +dx, 0.0f);
}
}
@@ -4887,7 +4886,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (mMarquee != null && mMarquee.shouldDrawGhost()) {
- canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
+ final int dx = (int) mMarquee.getGhostOffset();
+ canvas.translate(isLayoutRtl ? -dx : dx, 0.0f);
layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
}
@@ -5634,13 +5634,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
physicalWidth, false);
}
- /** @hide */
@Override
- public void onResolvedLayoutDirectionReset() {
+ public void onRtlPropertiesChanged(int layoutDirection) {
if (mLayoutAlignment != null) {
- int resolvedTextAlignment = getResolvedTextAlignment();
- if (resolvedTextAlignment == TEXT_ALIGNMENT_VIEW_START ||
- resolvedTextAlignment == TEXT_ALIGNMENT_VIEW_END) {
+ if (mResolvedTextAlignment == TEXT_ALIGNMENT_VIEW_START ||
+ mResolvedTextAlignment == TEXT_ALIGNMENT_VIEW_END) {
mLayoutAlignment = null;
}
}
@@ -5648,8 +5646,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private Layout.Alignment getLayoutAlignment() {
if (mLayoutAlignment == null) {
- int textAlign = getResolvedTextAlignment();
- switch (textAlign) {
+ mResolvedTextAlignment = getTextAlignment();
+ switch (mResolvedTextAlignment) {
case TEXT_ALIGNMENT_GRAVITY:
switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
case Gravity.START:
@@ -5682,11 +5680,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mLayoutAlignment = Layout.Alignment.ALIGN_CENTER;
break;
case TEXT_ALIGNMENT_VIEW_START:
- mLayoutAlignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+ mLayoutAlignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
break;
case TEXT_ALIGNMENT_VIEW_END:
- mLayoutAlignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+ mLayoutAlignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
break;
case TEXT_ALIGNMENT_INHERIT:
@@ -5735,7 +5733,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (mTextDir == null) {
- resolveTextDirection();
+ mTextDir = getTextDirectionHeuristic();
}
mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
@@ -5997,7 +5995,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
if (mTextDir == null) {
- resolveTextDirection();
+ getTextDirectionHeuristic();
}
int des = -1;
@@ -7481,12 +7479,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mMarquee != null && !mMarquee.isStopped()) {
final Marquee marquee = mMarquee;
if (marquee.shouldDrawLeftFade()) {
- return marquee.mScroll / getHorizontalFadingEdgeLength();
+ final float scroll = marquee.getScroll();
+ return scroll / getHorizontalFadingEdgeLength();
} else {
return 0.0f;
}
} else if (getLineCount() == 1) {
- final int layoutDirection = getResolvedLayoutDirection();
+ final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
@@ -7509,9 +7508,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
if (mMarquee != null && !mMarquee.isStopped()) {
final Marquee marquee = mMarquee;
- return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength();
+ final float maxFadeScroll = marquee.getMaxFadeScroll();
+ final float scroll = marquee.getScroll();
+ return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
} else if (getLineCount() == 1) {
- final int layoutDirection = getResolvedLayoutDirection();
+ final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
@@ -8180,49 +8181,38 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return mEditor.mInBatchEditControllers;
}
- /** @hide */
- @Override
- public void onResolvedTextDirectionChanged() {
+ TextDirectionHeuristic getTextDirectionHeuristic() {
if (hasPasswordTransformationMethod()) {
// TODO: take care of the content direction to show the password text and dots justified
// to the left or to the right
- mTextDir = TextDirectionHeuristics.LOCALE;
- return;
+ return TextDirectionHeuristics.LOCALE;
}
// Always need to resolve layout direction first
- final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
// Now, we can select the heuristic
- int textDir = getResolvedTextDirection();
- switch (textDir) {
+ switch (getTextDirection()) {
default:
case TEXT_DIRECTION_FIRST_STRONG:
- mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
+ return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
TextDirectionHeuristics.FIRSTSTRONG_LTR);
- break;
case TEXT_DIRECTION_ANY_RTL:
- mTextDir = TextDirectionHeuristics.ANYRTL_LTR;
- break;
+ return TextDirectionHeuristics.ANYRTL_LTR;
case TEXT_DIRECTION_LTR:
- mTextDir = TextDirectionHeuristics.LTR;
- break;
+ return TextDirectionHeuristics.LTR;
case TEXT_DIRECTION_RTL:
- mTextDir = TextDirectionHeuristics.RTL;
- break;
+ return TextDirectionHeuristics.RTL;
case TEXT_DIRECTION_LOCALE:
- mTextDir = TextDirectionHeuristics.LOCALE;
- break;
+ return TextDirectionHeuristics.LOCALE;
}
}
/**
- * Subclasses will need to override this method to implement their own way of resolving
- * drawables depending on the layout direction.
- *
- * A call to the super method will be required from the subclasses implementation.
+ * @hide
*/
- protected void resolveDrawables() {
+ @Override
+ public void onResolveDrawables(int layoutDirection) {
// No need to resolve twice
if (mResolvedDrawables) {
return;
@@ -8238,7 +8228,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
Drawables dr = mDrawables;
- switch(getResolvedLayoutDirection()) {
+ switch(layoutDirection) {
case LAYOUT_DIRECTION_RTL:
if (dr.mDrawableStart != null) {
dr.mDrawableRight = dr.mDrawableStart;
@@ -8270,9 +8260,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
break;
}
+ updateDrawablesLayoutDirection(dr, layoutDirection);
mResolvedDrawables = true;
}
+ private void updateDrawablesLayoutDirection(Drawables dr, int layoutDirection) {
+ if (dr.mDrawableLeft != null) {
+ dr.mDrawableLeft.setLayoutDirection(layoutDirection);
+ }
+ if (dr.mDrawableRight != null) {
+ dr.mDrawableRight.setLayoutDirection(layoutDirection);
+ }
+ if (dr.mDrawableTop != null) {
+ dr.mDrawableTop.setLayoutDirection(layoutDirection);
+ }
+ if (dr.mDrawableBottom != null) {
+ dr.mDrawableBottom.setLayoutDirection(layoutDirection);
+ }
+ }
+
protected void resetResolvedDrawables() {
mResolvedDrawables = false;
}
@@ -8341,7 +8347,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@Override
public CharSequence getIterableTextForAccessibility() {
- if (getContentDescription() == null) {
+ if (!TextUtils.isEmpty(mText)) {
if (!(mText instanceof Spannable)) {
setText(mText, BufferType.SPANNABLE);
}
@@ -8593,13 +8599,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private byte mStatus = MARQUEE_STOPPED;
private final float mScrollUnit;
private float mMaxScroll;
- float mMaxFadeScroll;
+ private float mMaxFadeScroll;
private float mGhostStart;
private float mGhostOffset;
private float mFadeStop;
private int mRepeatLimit;
- float mScroll;
+ private float mScroll;
Marquee(TextView v) {
final float density = v.getContext().getResources().getDisplayMetrics().density;
@@ -8691,6 +8697,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return mGhostOffset;
}
+ float getScroll() {
+ return mScroll;
+ }
+
+ float getMaxFadeScroll() {
+ return mMaxFadeScroll;
+ }
+
boolean shouldDrawLeftFade() {
return mScroll <= mFadeStop;
}
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index cb9ed61..e6796cb 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -172,7 +172,7 @@ public class TimePicker extends FrameLayout {
mMinuteSpinner.setMinValue(0);
mMinuteSpinner.setMaxValue(59);
mMinuteSpinner.setOnLongPressUpdateInterval(100);
- mMinuteSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
+ mMinuteSpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
mMinuteSpinner.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
public void onValueChange(NumberPicker spinner, int oldVal, int newVal) {
updateInputState();
@@ -500,7 +500,7 @@ public class TimePicker extends FrameLayout {
if (is24HourView()) {
mHourSpinner.setMinValue(0);
mHourSpinner.setMaxValue(23);
- mHourSpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER);
+ mHourSpinner.setFormatter(NumberPicker.getTwoDigitFormatter());
} else {
mHourSpinner.setMinValue(1);
mHourSpinner.setMaxValue(12);
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index fafc113..485bd37 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -19,6 +19,7 @@ package android.widget;
import android.app.INotificationManager;
import android.app.ITransientNotification;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.os.Handler;
@@ -29,7 +30,6 @@ import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
-import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -305,12 +305,14 @@ public class Toast {
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
+ @Override
public void run() {
handleShow();
}
};
final Runnable mHide = new Runnable() {
+ @Override
public void run() {
handleHide();
// Don't do this in handleHide() because it is also invoked by handleShow()
@@ -329,8 +331,8 @@ public class Toast {
View mView;
View mNextView;
-
- WindowManagerImpl mWM;
+
+ WindowManager mWM;
TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
@@ -350,6 +352,7 @@ public class Toast {
/**
* schedule handleShow into the right thread
*/
+ @Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
@@ -358,6 +361,7 @@ public class Toast {
/**
* schedule handleHide into the right thread
*/
+ @Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
@@ -370,8 +374,12 @@ public class Toast {
// remove the old view if necessary
handleHide();
mView = mNextView;
- mWM = WindowManagerImpl.getDefault();
- final int gravity = mGravity;
+ mWM = (WindowManager)mView.getContext().getApplicationContext()
+ .getSystemService(Context.WINDOW_SERVICE);
+ // We can resolve the Gravity here by using the Locale for getting
+ // the layout direction
+ final Configuration config = mView.getContext().getResources().getConfiguration();
+ final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
mParams.gravity = gravity;
if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
mParams.horizontalWeight = 1.0f;
diff --git a/core/java/android/widget/TwoLineListItem.java b/core/java/android/widget/TwoLineListItem.java
index e707ea3..f7e5266 100644
--- a/core/java/android/widget/TwoLineListItem.java
+++ b/core/java/android/widget/TwoLineListItem.java
@@ -37,7 +37,11 @@ import android.widget.RelativeLayout;
* layout for this object.
*
* @attr ref android.R.styleable#TwoLineListItem_mode
+ *
+ * @deprecated This class can be implemented easily by apps using a {@link RelativeLayout}
+ * or a {@link LinearLayout}.
*/
+@Deprecated
@Widget
public class TwoLineListItem extends RelativeLayout {
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 0fba498..7c8196d 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -26,6 +26,7 @@ import android.media.MediaPlayer;
import android.media.Metadata;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaPlayer.OnInfoListener;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
@@ -84,6 +85,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
private MediaPlayer.OnPreparedListener mOnPreparedListener;
private int mCurrentBufferPercentage;
private OnErrorListener mOnErrorListener;
+ private OnInfoListener mOnInfoListener;
private int mSeekWhenPrepared; // recording the seek position while preparing
private boolean mCanPause;
private boolean mCanSeekBack;
@@ -230,6 +232,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mDuration = -1;
mMediaPlayer.setOnCompletionListener(mCompletionListener);
mMediaPlayer.setOnErrorListener(mErrorListener);
+ mMediaPlayer.setOnInfoListener(mOnInfoListener);
mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
mCurrentBufferPercentage = 0;
mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
@@ -281,6 +284,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mVideoHeight = mp.getVideoHeight();
if (mVideoWidth != 0 && mVideoHeight != 0) {
getHolder().setFixedSize(mVideoWidth, mVideoHeight);
+ requestLayout();
}
}
};
@@ -455,6 +459,16 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mOnErrorListener = l;
}
+ /**
+ * Register a callback to be invoked when an informational event
+ * occurs during playback or setup.
+ *
+ * @param l The callback that will be run
+ */
+ public void setOnInfoListener(OnInfoListener l) {
+ mOnInfoListener = l;
+ }
+
SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
{
public void surfaceChanged(SurfaceHolder holder, int format,
diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java
index 6a68240..eee914e 100644
--- a/core/java/android/widget/ViewAnimator.java
+++ b/core/java/android/widget/ViewAnimator.java
@@ -329,8 +329,21 @@ public class ViewAnimator extends FrameLayout {
}
/**
+ * Returns whether the current View should be animated the first time the ViewAnimator
+ * is displayed.
+ *
+ * @return true if the current View will be animated the first time it is displayed,
+ * false otherwise.
+ *
+ * @see #setAnimateFirstView(boolean)
+ */
+ public boolean getAnimateFirstView() {
+ return mAnimateFirstTime;
+ }
+
+ /**
* Indicates whether the current View should be animated the first time
- * the ViewAnimation is displayed.
+ * the ViewAnimator is displayed.
*
* @param animate True to animate the current View the first time it is displayed,
* false otherwise.
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index 02dc27b..a89c9c1 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -242,7 +242,7 @@ public class ZoomButtonsController implements View.OnTouchListener {
private FrameLayout createContainer() {
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
// Controls are positioned BOTTOM | CENTER with respect to the owner view.
- lp.gravity = Gravity.TOP | Gravity.LEFT;
+ lp.gravity = Gravity.TOP | Gravity.START;
lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE |
LayoutParams.FLAG_NOT_FOCUSABLE |
LayoutParams.FLAG_LAYOUT_NO_LIMITS |
diff --git a/core/java/android/widget/package.html b/core/java/android/widget/package.html
index 7d94a4b..91d327c 100644
--- a/core/java/android/widget/package.html
+++ b/core/java/android/widget/package.html
@@ -1,11 +1,16 @@
<HTML>
<BODY>
+<p>
The widget package contains (mostly visual) UI elements to use
-on your Application screen. You can design your own <p>
+on your Application screen. You can also design your own.
+</p>
+
+<p>
To create your own widget, extend {@link android.view.View} or a subclass. To
use your widget in layout XML, there are two additional files for you to
create. Here is a list of files you'll need to create to implement a custom
widget:
+</p>
<ul>
<li><b>Java implementation file</b> - This is the file that implements the
behavior of the widget. If you can instantiate the object from layout XML,
@@ -19,14 +24,16 @@ another in their layout XML.</li>
res/layout/ that describes the layout of your widget. You could also do
this in code in your Java file.</li>
</ul>
+
+<p>
ApiDemos sample application has an example of creating a custom layout XML
tag, LabelView. See the following files that demonstrate implementing and using
-a custom widget:</p>
+a custom widget:
+</p>
<ul>
- <li><strong>LabelView.java</strong> - The implentation file</li>
+ <li><strong>LabelView.java</strong> - The implementation file</li>
<li><strong>res/values/attrs.xml</strong> - Definition file</li>
- <li><strong>res/layout/custom_view_1.xml</strong> - Layout
-file</li>
+ <li><strong>res/layout/custom_view_1.xml</strong> - Layout file</li>
</ul>
</BODY>
</HTML>
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index 56b5937..43a02cf 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -764,6 +764,7 @@ public class AlertController {
public DialogInterface.OnClickListener mNeutralButtonListener;
public boolean mCancelable;
public DialogInterface.OnCancelListener mOnCancelListener;
+ public DialogInterface.OnDismissListener mOnDismissListener;
public DialogInterface.OnKeyListener mOnKeyListener;
public CharSequence[] mItems;
public ListAdapter mAdapter;
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 6a0cd36..1a76461f 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -58,14 +58,14 @@ interface IBatteryStats {
void noteBluetoothOff();
void noteFullWifiLockAcquired(int uid);
void noteFullWifiLockReleased(int uid);
- void noteScanWifiLockAcquired(int uid);
- void noteScanWifiLockReleased(int uid);
+ void noteWifiScanStarted(int uid);
+ void noteWifiScanStopped(int uid);
void noteWifiMulticastEnabled(int uid);
void noteWifiMulticastDisabled(int uid);
void noteFullWifiLockAcquiredFromSource(in WorkSource ws);
void noteFullWifiLockReleasedFromSource(in WorkSource ws);
- void noteScanWifiLockAcquiredFromSource(in WorkSource ws);
- void noteScanWifiLockReleasedFromSource(in WorkSource ws);
+ void noteWifiScanStartedFromSource(in WorkSource ws);
+ void noteWifiScanStoppedFromSource(in WorkSource ws);
void noteWifiMulticastEnabledFromSource(in WorkSource ws);
void noteWifiMulticastDisabledFromSource(in WorkSource ws);
void noteNetworkInterfaceType(String iface, int type);
diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
index 52cb679..f173327 100644
--- a/core/java/com/android/internal/app/LocalePicker.java
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -28,9 +28,12 @@ import android.content.res.Resources;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
+import android.widget.TextView;
import java.text.Collator;
import java.util.Arrays;
@@ -86,7 +89,7 @@ public class LocalePicker extends ListFragment {
}
public static ArrayAdapter<LocaleInfo> constructAdapter(Context context,
- int layoutId, int fieldId) {
+ final int layoutId, final int fieldId) {
final Resources resources = context.getResources();
final String[] locales = Resources.getSystem().getAssets().getLocales();
final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
@@ -154,7 +157,29 @@ public class LocalePicker extends ListFragment {
localeInfos[i] = preprocess[i];
}
Arrays.sort(localeInfos);
- return new ArrayAdapter<LocaleInfo>(context, layoutId, fieldId, localeInfos);
+
+ final LayoutInflater inflater =
+ (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ return new ArrayAdapter<LocaleInfo>(context, layoutId, fieldId, localeInfos) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+ TextView text;
+ if (convertView == null) {
+ view = inflater.inflate(layoutId, parent, false);
+ text = (TextView) view.findViewById(fieldId);
+ view.setTag(text);
+ } else {
+ view = convertView;
+ text = (TextView) view.getTag();
+ }
+ LocaleInfo item = getItem(position);
+ text.setText(item.toString());
+ text.setTextLocale(item.getLocale());
+
+ return view;
+ }
+ };
}
private static String toTitleCase(String s) {
@@ -218,10 +243,9 @@ public class LocalePicker extends ListFragment {
IActivityManager am = ActivityManagerNative.getDefault();
Configuration config = am.getConfiguration();
- config.locale = locale;
-
- // indicate this isn't some passing default - the user wants this remembered
- config.userSetLocale = true;
+ // Will set userSetLocale to indicate this isn't some passing default - the user
+ // wants this remembered
+ config.setLocale(locale);
am.updateConfiguration(config);
// Trigger the dirty bit for the Settings Provider.
@@ -230,4 +254,4 @@ public class LocalePicker extends ListFragment {
// Intentionally left blank
}
}
-} \ No newline at end of file
+}
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index f010d7b..386f387 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
@@ -25,7 +25,7 @@ import android.app.MediaRouteActionProvider;
import android.app.MediaRouteButton;
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.media.AudioManager;
+import android.hardware.display.DisplayManager;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteCategory;
import android.media.MediaRouter.RouteGroup;
@@ -70,6 +70,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
};
MediaRouter mRouter;
+ DisplayManager mDisplayService;
private int mRouteTypes;
private LayoutInflater mInflater;
@@ -97,6 +98,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
public void onAttach(Activity activity) {
super.onAttach(activity);
mRouter = (MediaRouter) activity.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+ mDisplayService = (DisplayManager) activity.getSystemService(Context.DISPLAY_SERVICE);
}
@Override
@@ -119,6 +121,15 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
public void setRouteTypes(int types) {
mRouteTypes = types;
+ if ((mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0 && mDisplayService == null) {
+ final Context activity = getActivity();
+ if (activity != null) {
+ mDisplayService = (DisplayManager) activity.getSystemService(
+ Context.DISPLAY_SERVICE);
+ }
+ } else {
+ mDisplayService = null;
+ }
}
void updateVolume() {
@@ -194,6 +205,9 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
@Override
public void onResume() {
super.onResume();
+ if (mDisplayService != null) {
+ mDisplayService.scanWifiDisplays();
+ }
}
private static class ViewHolder {
@@ -253,7 +267,9 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
final RouteCategory cat = mRouter.getCategoryAt(i);
routes = cat.getRoutes(mCatRouteList);
- mItems.add(cat);
+ if (!cat.isSystem()) {
+ mItems.add(cat);
+ }
if (cat == mCategoryEditingGroups) {
addGroupEditingCategoryRoutes(routes);
@@ -370,6 +386,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
public boolean isEnabled(int position) {
switch (getItemViewType(position)) {
case VIEW_ROUTE:
+ return ((RouteInfo) mItems.get(position)).isEnabled();
case VIEW_GROUPING_ROUTE:
case VIEW_GROUPING_DONE:
return true;
@@ -434,6 +451,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
}
convertView.setActivated(position == mSelectedItemPosition);
+ convertView.setEnabled(isEnabled(position));
return convertView;
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7334ac3..4ad0819 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -37,7 +37,7 @@ import android.os.Bundle;
import android.os.PatternMatcher;
import android.os.Process;
import android.os.RemoteException;
-import android.os.UserId;
+import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -76,6 +76,7 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
private int mIconDpi;
private int mIconSize;
private int mMaxColumns;
+ private int mLastSelected = GridView.INVALID_POSITION;
private boolean mRegistered;
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@@ -132,7 +133,7 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
mAdapter = new ResolveListAdapter(this, intent, initialIntents, rList,
mLaunchedFromUid);
int count = mAdapter.getCount();
- if (mLaunchedFromUid < 0 || UserId.isIsolated(mLaunchedFromUid)) {
+ if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
// Gulp!
finish();
return;
@@ -149,7 +150,8 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
resizeGrid();
} else if (count == 1) {
- startActivity(mAdapter.intentForPosition(0));
+ startActivityAsUser(mAdapter.intentForPosition(0),
+ new UserHandle(UserHandle.getUserId(mLaunchedFromUid)));
mPackageMonitor.unregister();
mRegistered = false;
finish();
@@ -247,6 +249,7 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
if (mAlwaysUseOption) {
final int checkedPos = mGrid.getCheckedItemPosition();
final boolean enabled = checkedPos != GridView.INVALID_POSITION;
+ mLastSelected = checkedPos;
mAlwaysButton.setEnabled(enabled);
mOnceButton.setEnabled(enabled);
if (enabled) {
@@ -257,14 +260,15 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (mAlwaysUseOption) {
- final int checkedPos = mGrid.getCheckedItemPosition();
- final boolean enabled = checkedPos != GridView.INVALID_POSITION;
- mAlwaysButton.setEnabled(enabled);
- mOnceButton.setEnabled(enabled);
- if (enabled) {
+ final int checkedPos = mGrid.getCheckedItemPosition();
+ final boolean hasValidSelection = checkedPos != GridView.INVALID_POSITION;
+ if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) {
+ mAlwaysButton.setEnabled(hasValidSelection);
+ mOnceButton.setEnabled(hasValidSelection);
+ if (hasValidSelection) {
mGrid.smoothScrollToPosition(checkedPos);
}
+ mLastSelected = checkedPos;
} else {
startSelected(position, false);
}
@@ -360,19 +364,20 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
if (r.match > bestMatch) bestMatch = r.match;
}
getPackageManager().addPreferredActivity(filter, bestMatch, set,
- intent.getComponent());
+ intent.getComponent(), UserHandle.getUserId(mLaunchedFromUid));
}
}
if (intent != null) {
- startActivity(intent);
+ startActivityAsUser(intent, new UserHandle(UserHandle.getUserId(mLaunchedFromUid)));
}
}
void showAppDetails(ResolveInfo ri) {
Intent in = new Intent().setAction("android.settings.APPLICATION_DETAILS_SETTINGS")
- .setData(Uri.fromParts("package", ri.activityInfo.packageName, null));
- startActivity(in);
+ .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+ startActivityAsUser(in, new UserHandle(UserHandle.getUserId(mLaunchedFromUid)));
}
private final class DisplayResolveInfo {
@@ -431,9 +436,10 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte
if (mBaseResolveList != null) {
mCurrentResolveList = mBaseResolveList;
} else {
- mCurrentResolveList = mPm.queryIntentActivities(
+ mCurrentResolveList = mPm.queryIntentActivitiesAsUser(
mIntent, PackageManager.MATCH_DEFAULT_ONLY
- | (mAlwaysUseOption ? PackageManager.GET_RESOLVED_FILTER : 0));
+ | (mAlwaysUseOption ? PackageManager.GET_RESOLVED_FILTER : 0),
+ UserHandle.getUserId(mLaunchedFromUid));
// Filter out any activities that the launched uid does not
// have permission for. We don't do this when we have an explicit
// list of resolved activities, because that only happens when
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
index 216d985..78b4466 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
@@ -24,6 +24,7 @@ import android.widget.RemoteViews;
oneway interface IAppWidgetHost {
void updateAppWidget(int appWidgetId, in RemoteViews views);
void providerChanged(int appWidgetId, in AppWidgetProviderInfo info);
+ void providersChanged();
void viewDataChanged(int appWidgetId, int viewId);
}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 7df45cf..cfb16fa 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -52,9 +52,9 @@ interface IAppWidgetService {
AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId);
boolean hasBindAppWidgetPermission(in String packageName);
void setBindAppWidgetPermission(in String packageName, in boolean permission);
- void bindAppWidgetId(int appWidgetId, in ComponentName provider);
+ void bindAppWidgetId(int appWidgetId, in ComponentName provider, in Bundle options);
boolean bindAppWidgetIdIfAllowed(
- in String packageName, int appWidgetId, in ComponentName provider);
+ in String packageName, int appWidgetId, in ComponentName provider, in Bundle options);
void bindRemoteViewsService(int appWidgetId, in Intent intent, in IBinder connection);
void unbindRemoteViewsService(int appWidgetId, in Intent intent);
int[] getAppWidgetIds(in ComponentName provider);
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 246b0c9..c5e7d9d 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -49,6 +49,7 @@ public class PackageHelper {
public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4;
public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5;
public static final int RECOMMEND_FAILED_INVALID_URI = -6;
+ public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7;
private static final boolean localLOGV = true;
private static final String TAG = "PackageHelper";
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index f41fcc6..20ecace 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -24,6 +24,7 @@ import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.UserHandle;
import java.util.HashSet;
@@ -49,6 +50,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
sPackageFilt.addAction(Intent.ACTION_UID_REMOVED);
sPackageFilt.addDataScheme("package");
sNonDataFilt.addAction(Intent.ACTION_UID_REMOVED);
+ sNonDataFilt.addAction(Intent.ACTION_USER_STOPPED);
sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
}
@@ -61,11 +63,17 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
String[] mAppearingPackages;
String[] mModifiedPackages;
int mChangeType;
+ int mChangeUserId = UserHandle.USER_NULL;
boolean mSomePackagesChanged;
-
+
String[] mTempArray = new String[1];
-
+
public void register(Context context, Looper thread, boolean externalStorage) {
+ register(context, thread, null, externalStorage);
+ }
+
+ public void register(Context context, Looper thread, UserHandle user,
+ boolean externalStorage) {
if (mRegisteredContext != null) {
throw new IllegalStateException("Already registered");
}
@@ -83,10 +91,19 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
} else {
mRegisteredHandler = new Handler(thread);
}
- context.registerReceiver(this, sPackageFilt, null, mRegisteredHandler);
- context.registerReceiver(this, sNonDataFilt, null, mRegisteredHandler);
- if (externalStorage) {
- context.registerReceiver(this, sExternalFilt, null, mRegisteredHandler);
+ if (user != null) {
+ context.registerReceiverAsUser(this, user, sPackageFilt, null, mRegisteredHandler);
+ context.registerReceiverAsUser(this, user, sNonDataFilt, null, mRegisteredHandler);
+ if (externalStorage) {
+ context.registerReceiverAsUser(this, user, sExternalFilt, null,
+ mRegisteredHandler);
+ }
+ } else {
+ context.registerReceiver(this, sPackageFilt, null, mRegisteredHandler);
+ context.registerReceiver(this, sNonDataFilt, null, mRegisteredHandler);
+ if (externalStorage) {
+ context.registerReceiver(this, sExternalFilt, null, mRegisteredHandler);
+ }
}
}
@@ -111,16 +128,29 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
public void onBeginPackageChanges() {
}
-
+
+ /**
+ * Called when a package is really added (and not replaced).
+ */
public void onPackageAdded(String packageName, int uid) {
}
-
+
+ /**
+ * Called when a package is really removed (and not replaced).
+ */
public void onPackageRemoved(String packageName, int uid) {
}
-
+
+ /**
+ * Called when a package is really removed (and not replaced) for
+ * all users on the device.
+ */
+ public void onPackageRemovedAllUsers(String packageName, int uid) {
+ }
+
public void onPackageUpdateStarted(String packageName, int uid) {
}
-
+
public void onPackageUpdateFinished(String packageName, int uid) {
}
@@ -130,6 +160,9 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
return false;
}
+
+ public void onHandleUserStop(Intent intent, int userHandle) {
+ }
public void onUidRemoved(int uid) {
}
@@ -144,10 +177,16 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
public static final int PACKAGE_UPDATING = 1;
public static final int PACKAGE_TEMPORARY_CHANGE = 2;
public static final int PACKAGE_PERMANENT_CHANGE = 3;
-
+
+ /**
+ * Called when a package disappears for any reason.
+ */
public void onPackageDisappeared(String packageName, int reason) {
}
-
+
+ /**
+ * Called when a package appears for any reason.
+ */
public void onPackageAppeared(String packageName, int reason) {
}
@@ -204,7 +243,11 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
public void onFinishPackageChanges() {
}
-
+
+ public int getChangingUserId() {
+ return mChangeUserId;
+ }
+
String getPackageName(Intent intent) {
Uri uri = intent.getData();
String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
@@ -213,6 +256,12 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
+ mChangeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_NULL);
+ if (mChangeUserId == UserHandle.USER_NULL) {
+ throw new IllegalArgumentException(
+ "Intent broadcast does not contain user handle: " + intent);
+ }
onBeginPackageChanges();
mDisappearingPackages = mAppearingPackages = null;
@@ -265,6 +314,9 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
// it when it is re-added.
mSomePackagesChanged = true;
onPackageRemoved(pkg, uid);
+ if (intent.getBooleanExtra(Intent.EXTRA_REMOVED_FOR_ALL_USERS, false)) {
+ onPackageRemovedAllUsers(pkg, uid);
+ }
}
onPackageDisappeared(pkg, mChangeType);
}
@@ -295,6 +347,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
intent.getIntExtra(Intent.EXTRA_UID, 0), true);
} else if (Intent.ACTION_UID_REMOVED.equals(action)) {
onUidRemoved(intent.getIntExtra(Intent.EXTRA_UID, 0));
+ } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+ if (intent.hasExtra(Intent.EXTRA_USER_HANDLE)) {
+ onHandleUserStop(intent, intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ }
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) {
String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
mAppearingPackages = pkgList;
@@ -324,5 +380,6 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
}
onFinishPackageChanges();
+ mChangeUserId = UserHandle.USER_NULL;
}
}
diff --git a/core/java/com/android/internal/net/LegacyVpnInfo.java b/core/java/com/android/internal/net/LegacyVpnInfo.java
index b620aba..d6f6d0b 100644
--- a/core/java/com/android/internal/net/LegacyVpnInfo.java
+++ b/core/java/com/android/internal/net/LegacyVpnInfo.java
@@ -17,8 +17,10 @@
package com.android.internal.net;
import android.app.PendingIntent;
+import android.net.NetworkInfo;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
/**
* A simple container used to carry information of the ongoing legacy VPN.
@@ -27,6 +29,8 @@ import android.os.Parcelable;
* @hide
*/
public class LegacyVpnInfo implements Parcelable {
+ private static final String TAG = "LegacyVpnInfo";
+
public static final int STATE_DISCONNECTED = 0;
public static final int STATE_INITIALIZING = 1;
public static final int STATE_CONNECTING = 2;
@@ -66,4 +70,25 @@ public class LegacyVpnInfo implements Parcelable {
return new LegacyVpnInfo[size];
}
};
+
+ /**
+ * Return best matching {@link LegacyVpnInfo} state based on given
+ * {@link NetworkInfo}.
+ */
+ public static int stateFromNetworkInfo(NetworkInfo info) {
+ switch (info.getDetailedState()) {
+ case CONNECTING:
+ return STATE_CONNECTING;
+ case CONNECTED:
+ return STATE_CONNECTED;
+ case DISCONNECTED:
+ return STATE_DISCONNECTED;
+ case FAILED:
+ return STATE_FAILED;
+ default:
+ Log.w(TAG, "Unhandled state " + info.getDetailedState()
+ + " ; treating as disconnected");
+ return STATE_DISCONNECTED;
+ }
+ }
}
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index d6f9e07..956653b 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -22,6 +22,8 @@ import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
import java.util.List;
/**
@@ -45,13 +47,14 @@ public class VpnConfig implements Parcelable {
}
public static PendingIntent getIntentForStatusPanel(Context context, VpnConfig config) {
+ Preconditions.checkNotNull(config);
+
Intent intent = new Intent();
intent.setClassName(DIALOGS_PACKAGE, DIALOGS_PACKAGE + ".ManageDialog");
intent.putExtra("config", config);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY |
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- return PendingIntent.getActivity(context, 0, intent, (config == null) ?
- PendingIntent.FLAG_NO_CREATE : PendingIntent.FLAG_CANCEL_CURRENT);
+ return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
}
public String user;
@@ -64,6 +67,7 @@ public class VpnConfig implements Parcelable {
public List<String> searchDomains;
public PendingIntent configureIntent;
public long startTime = -1;
+ public boolean legacy;
@Override
public int describeContents() {
@@ -82,6 +86,7 @@ public class VpnConfig implements Parcelable {
out.writeStringList(searchDomains);
out.writeParcelable(configureIntent, flags);
out.writeLong(startTime);
+ out.writeInt(legacy ? 1 : 0);
}
public static final Parcelable.Creator<VpnConfig> CREATOR =
@@ -99,6 +104,7 @@ public class VpnConfig implements Parcelable {
config.searchDomains = in.createStringArrayList();
config.configureIntent = in.readParcelable(null);
config.startTime = in.readLong();
+ config.legacy = in.readInt() != 0;
return config;
}
diff --git a/core/java/com/android/internal/net/VpnProfile.aidl b/core/java/com/android/internal/net/VpnProfile.aidl
new file mode 100644
index 0000000..a072160
--- /dev/null
+++ b/core/java/com/android/internal/net/VpnProfile.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 com.android.internal.net;
+
+parcelable VpnProfile;
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
new file mode 100644
index 0000000..7287327
--- /dev/null
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -0,0 +1,181 @@
+/*
+ * 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.android.internal.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.nio.charset.Charsets;
+
+/**
+ * Parcel-like entity class for VPN profiles. To keep things simple, all
+ * fields are package private. Methods are provided for serialization, so
+ * storage can be implemented easily. Two rules are set for this class.
+ * First, all fields must be kept non-null. Second, always make a copy
+ * using clone() before modifying.
+ *
+ * @hide
+ */
+public class VpnProfile implements Cloneable, Parcelable {
+ private static final String TAG = "VpnProfile";
+
+ // Match these constants with R.array.vpn_types.
+ public static final int TYPE_PPTP = 0;
+ public static final int TYPE_L2TP_IPSEC_PSK = 1;
+ public static final int TYPE_L2TP_IPSEC_RSA = 2;
+ public static final int TYPE_IPSEC_XAUTH_PSK = 3;
+ public static final int TYPE_IPSEC_XAUTH_RSA = 4;
+ public static final int TYPE_IPSEC_HYBRID_RSA = 5;
+ public static final int TYPE_MAX = 5;
+
+ // Entity fields.
+ public final String key; // -1
+ public String name = ""; // 0
+ public int type = TYPE_PPTP; // 1
+ public String server = ""; // 2
+ public String username = ""; // 3
+ public String password = ""; // 4
+ public String dnsServers = ""; // 5
+ public String searchDomains = ""; // 6
+ public String routes = ""; // 7
+ public boolean mppe = true; // 8
+ public String l2tpSecret = ""; // 9
+ public String ipsecIdentifier = "";// 10
+ public String ipsecSecret = ""; // 11
+ public String ipsecUserCert = ""; // 12
+ public String ipsecCaCert = ""; // 13
+ public String ipsecServerCert = "";// 14
+
+ // Helper fields.
+ public boolean saveLogin = false;
+
+ public VpnProfile(String key) {
+ this.key = key;
+ }
+
+ public static VpnProfile decode(String key, byte[] value) {
+ try {
+ if (key == null) {
+ return null;
+ }
+
+ String[] values = new String(value, Charsets.UTF_8).split("\0", -1);
+ // There can be 14 or 15 values in ICS MR1.
+ if (values.length < 14 || values.length > 15) {
+ return null;
+ }
+
+ VpnProfile profile = new VpnProfile(key);
+ profile.name = values[0];
+ profile.type = Integer.valueOf(values[1]);
+ if (profile.type < 0 || profile.type > TYPE_MAX) {
+ return null;
+ }
+ profile.server = values[2];
+ profile.username = values[3];
+ profile.password = values[4];
+ profile.dnsServers = values[5];
+ profile.searchDomains = values[6];
+ profile.routes = values[7];
+ profile.mppe = Boolean.valueOf(values[8]);
+ profile.l2tpSecret = values[9];
+ profile.ipsecIdentifier = values[10];
+ profile.ipsecSecret = values[11];
+ profile.ipsecUserCert = values[12];
+ profile.ipsecCaCert = values[13];
+ profile.ipsecServerCert = (values.length > 14) ? values[14] : "";
+
+ profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
+ return profile;
+ } catch (Exception e) {
+ // ignore
+ }
+ return null;
+ }
+
+ public byte[] encode() {
+ StringBuilder builder = new StringBuilder(name);
+ builder.append('\0').append(type);
+ builder.append('\0').append(server);
+ builder.append('\0').append(saveLogin ? username : "");
+ builder.append('\0').append(saveLogin ? password : "");
+ builder.append('\0').append(dnsServers);
+ builder.append('\0').append(searchDomains);
+ builder.append('\0').append(routes);
+ builder.append('\0').append(mppe);
+ builder.append('\0').append(l2tpSecret);
+ builder.append('\0').append(ipsecIdentifier);
+ builder.append('\0').append(ipsecSecret);
+ builder.append('\0').append(ipsecUserCert);
+ builder.append('\0').append(ipsecCaCert);
+ builder.append('\0').append(ipsecServerCert);
+ return builder.toString().getBytes(Charsets.UTF_8);
+ }
+
+ /**
+ * Test if profile is valid for lockdown, which requires IPv4 address for
+ * both server and DNS. Server hostnames would require using DNS before
+ * connection.
+ */
+ public boolean isValidLockdownProfile() {
+ try {
+ InetAddress.parseNumericAddress(server);
+
+ for (String dnsServer : dnsServers.split(" +")) {
+ InetAddress.parseNumericAddress(this.dnsServers);
+ }
+ if (TextUtils.isEmpty(dnsServers)) {
+ Log.w(TAG, "DNS required");
+ return false;
+ }
+
+ // Everything checked out above
+ return true;
+
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Invalid address", e);
+ return false;
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(key);
+ out.writeByteArray(encode());
+ }
+
+ public static final Creator<VpnProfile> CREATOR = new Creator<VpnProfile>() {
+ @Override
+ public VpnProfile createFromParcel(Parcel in) {
+ final String key = in.readString();
+ return decode(key, in.createByteArray());
+ }
+
+ @Override
+ public VpnProfile[] newArray(int size) {
+ return new VpnProfile[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index db752e9..94e7a06 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -87,7 +87,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 61 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 62 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -159,8 +159,8 @@ public final class BatteryStatsImpl extends BatteryStats {
= new SparseArray<ArrayList<StopwatchTimer>>();
final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<StopwatchTimer>();
final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<StopwatchTimer>();
- final ArrayList<StopwatchTimer> mScanWifiLockTimers = new ArrayList<StopwatchTimer>();
final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<StopwatchTimer>();
+ final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<StopwatchTimer>();
// Last partial timers we use for distributing CPU usage.
final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<StopwatchTimer>();
@@ -320,6 +320,18 @@ public final class BatteryStatsImpl extends BatteryStats {
Process.PROC_TAB_TERM|Process.PROC_OUT_LONG, // 5: totalTime
};
+ private static final int[] WAKEUP_SOURCES_FORMAT = new int[] {
+ Process.PROC_TAB_TERM|Process.PROC_OUT_STRING, // 0: name
+ Process.PROC_TAB_TERM|Process.PROC_COMBINE|
+ Process.PROC_OUT_LONG, // 1: count
+ Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+ Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+ Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+ Process.PROC_TAB_TERM|Process.PROC_COMBINE,
+ Process.PROC_TAB_TERM|Process.PROC_COMBINE
+ |Process.PROC_OUT_LONG, // 6: totalTime
+ };
+
private final String[] mProcWakelocksName = new String[3];
private final long[] mProcWakelocksData = new long[3];
@@ -1028,34 +1040,44 @@ public final class BatteryStatsImpl extends BatteryStats {
private final Map<String, KernelWakelockStats> readKernelWakelockStats() {
+ FileInputStream is;
byte[] buffer = new byte[8192];
int len;
+ boolean wakeup_sources = false;
try {
- FileInputStream is = new FileInputStream("/proc/wakelocks");
+ try {
+ is = new FileInputStream("/proc/wakelocks");
+ } catch (java.io.FileNotFoundException e) {
+ try {
+ is = new FileInputStream("/d/wakeup_sources");
+ wakeup_sources = true;
+ } catch (java.io.FileNotFoundException e2) {
+ return null;
+ }
+ }
+
len = is.read(buffer);
is.close();
+ } catch (java.io.IOException e) {
+ return null;
+ }
- if (len > 0) {
- int i;
- for (i=0; i<len; i++) {
- if (buffer[i] == '\0') {
- len = i;
- break;
- }
+ if (len > 0) {
+ int i;
+ for (i=0; i<len; i++) {
+ if (buffer[i] == '\0') {
+ len = i;
+ break;
}
}
- } catch (java.io.FileNotFoundException e) {
- return null;
- } catch (java.io.IOException e) {
- return null;
}
- return parseProcWakelocks(buffer, len);
+ return parseProcWakelocks(buffer, len, wakeup_sources);
}
private final Map<String, KernelWakelockStats> parseProcWakelocks(
- byte[] wlBuffer, int len) {
+ byte[] wlBuffer, int len, boolean wakeup_sources) {
String name;
int count;
long totalTime;
@@ -1092,12 +1114,20 @@ public final class BatteryStatsImpl extends BatteryStats {
if ((wlBuffer[j] & 0x80) != 0) wlBuffer[j] = (byte) '?';
}
boolean parsed = Process.parseProcLine(wlBuffer, startIndex, endIndex,
- PROC_WAKELOCKS_FORMAT, nameStringArray, wlData, null);
+ wakeup_sources ? WAKEUP_SOURCES_FORMAT :
+ PROC_WAKELOCKS_FORMAT,
+ nameStringArray, wlData, null);
name = nameStringArray[0];
count = (int) wlData[1];
- // convert nanoseconds to microseconds with rounding.
- totalTime = (wlData[2] + 500) / 1000;
+
+ if (wakeup_sources) {
+ // convert milliseconds to microseconds
+ totalTime = wlData[2] * 1000;
+ } else {
+ // convert nanoseconds to microseconds with rounding.
+ totalTime = (wlData[2] + 500) / 1000;
+ }
if (parsed && name.length() > 0) {
if (!m.containsKey(name)) {
@@ -2174,28 +2204,28 @@ public final class BatteryStatsImpl extends BatteryStats {
getUidStatsLocked(uid).noteFullWifiLockReleasedLocked();
}
- int mWifiScanLockNesting = 0;
+ int mWifiScanNesting = 0;
- public void noteScanWifiLockAcquiredLocked(int uid) {
- if (mWifiScanLockNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_LOCK_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan lock on to: "
+ public void noteWifiScanStartedLocked(int uid) {
+ if (mWifiScanNesting == 0) {
+ mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan started for: "
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(SystemClock.elapsedRealtime());
}
- mWifiScanLockNesting++;
- getUidStatsLocked(uid).noteScanWifiLockAcquiredLocked();
+ mWifiScanNesting++;
+ getUidStatsLocked(uid).noteWifiScanStartedLocked();
}
- public void noteScanWifiLockReleasedLocked(int uid) {
- mWifiScanLockNesting--;
- if (mWifiScanLockNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_LOCK_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan lock off to: "
+ public void noteWifiScanStoppedLocked(int uid) {
+ mWifiScanNesting--;
+ if (mWifiScanNesting == 0) {
+ mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan stopped for: "
+ Integer.toHexString(mHistoryCur.states));
addHistoryRecordLocked(SystemClock.elapsedRealtime());
}
- getUidStatsLocked(uid).noteScanWifiLockReleasedLocked();
+ getUidStatsLocked(uid).noteWifiScanStoppedLocked();
}
int mWifiMulticastNesting = 0;
@@ -2236,17 +2266,17 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public void noteScanWifiLockAcquiredFromSourceLocked(WorkSource ws) {
+ public void noteWifiScanStartedFromSourceLocked(WorkSource ws) {
int N = ws.size();
for (int i=0; i<N; i++) {
- noteScanWifiLockAcquiredLocked(ws.get(i));
+ noteWifiScanStartedLocked(ws.get(i));
}
}
- public void noteScanWifiLockReleasedFromSourceLocked(WorkSource ws) {
+ public void noteWifiScanStoppedFromSourceLocked(WorkSource ws) {
int N = ws.size();
for (int i=0; i<N; i++) {
- noteScanWifiLockReleasedLocked(ws.get(i));
+ noteWifiScanStoppedLocked(ws.get(i));
}
}
@@ -2360,8 +2390,8 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mFullWifiLockOut;
StopwatchTimer mFullWifiLockTimer;
- boolean mScanWifiLockOut;
- StopwatchTimer mScanWifiLockTimer;
+ boolean mWifiScanStarted;
+ StopwatchTimer mWifiScanTimer;
boolean mWifiMulticastEnabled;
StopwatchTimer mWifiMulticastTimer;
@@ -2405,8 +2435,8 @@ public final class BatteryStatsImpl extends BatteryStats {
mWifiRunningTimers, mUnpluggables);
mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
mFullWifiLockTimers, mUnpluggables);
- mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK,
- mScanWifiLockTimers, mUnpluggables);
+ mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN,
+ mWifiScanTimers, mUnpluggables);
mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
mWifiMulticastTimers, mUnpluggables);
mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
@@ -2518,22 +2548,22 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
- public void noteScanWifiLockAcquiredLocked() {
- if (!mScanWifiLockOut) {
- mScanWifiLockOut = true;
- if (mScanWifiLockTimer == null) {
- mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK,
- mScanWifiLockTimers, mUnpluggables);
+ public void noteWifiScanStartedLocked() {
+ if (!mWifiScanStarted) {
+ mWifiScanStarted = true;
+ if (mWifiScanTimer == null) {
+ mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN,
+ mWifiScanTimers, mUnpluggables);
}
- mScanWifiLockTimer.startRunningLocked(BatteryStatsImpl.this);
+ mWifiScanTimer.startRunningLocked(BatteryStatsImpl.this);
}
}
@Override
- public void noteScanWifiLockReleasedLocked() {
- if (mScanWifiLockOut) {
- mScanWifiLockOut = false;
- mScanWifiLockTimer.stopRunningLocked(BatteryStatsImpl.this);
+ public void noteWifiScanStoppedLocked() {
+ if (mWifiScanStarted) {
+ mWifiScanStarted = false;
+ mWifiScanTimer.stopRunningLocked(BatteryStatsImpl.this);
}
}
@@ -2614,11 +2644,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
- public long getScanWifiLockTime(long batteryRealtime, int which) {
- if (mScanWifiLockTimer == null) {
+ public long getWifiScanTime(long batteryRealtime, int which) {
+ if (mWifiScanTimer == null) {
return 0;
}
- return mScanWifiLockTimer.getTotalTimeLocked(batteryRealtime, which);
+ return mWifiScanTimer.getTotalTimeLocked(batteryRealtime, which);
}
@Override
@@ -2651,9 +2681,12 @@ public final class BatteryStatsImpl extends BatteryStats {
if (mUserActivityCounters == null) {
initUserActivityLocked();
}
- if (type < 0) type = 0;
- else if (type >= NUM_USER_ACTIVITY_TYPES) type = NUM_USER_ACTIVITY_TYPES-1;
- mUserActivityCounters[type].stepAtomic();
+ if (type >= 0 && type < NUM_USER_ACTIVITY_TYPES) {
+ mUserActivityCounters[type].stepAtomic();
+ } else {
+ Slog.w(TAG, "Unknown user activity type " + type + " was specified.",
+ new Throwable());
+ }
}
@Override
@@ -2698,9 +2731,9 @@ public final class BatteryStatsImpl extends BatteryStats {
active |= !mFullWifiLockTimer.reset(BatteryStatsImpl.this, false);
active |= mFullWifiLockOut;
}
- if (mScanWifiLockTimer != null) {
- active |= !mScanWifiLockTimer.reset(BatteryStatsImpl.this, false);
- active |= mScanWifiLockOut;
+ if (mWifiScanTimer != null) {
+ active |= !mWifiScanTimer.reset(BatteryStatsImpl.this, false);
+ active |= mWifiScanStarted;
}
if (mWifiMulticastTimer != null) {
active |= !mWifiMulticastTimer.reset(BatteryStatsImpl.this, false);
@@ -2791,8 +2824,8 @@ public final class BatteryStatsImpl extends BatteryStats {
if (mFullWifiLockTimer != null) {
mFullWifiLockTimer.detach();
}
- if (mScanWifiLockTimer != null) {
- mScanWifiLockTimer.detach();
+ if (mWifiScanTimer != null) {
+ mWifiScanTimer.detach();
}
if (mWifiMulticastTimer != null) {
mWifiMulticastTimer.detach();
@@ -2860,9 +2893,9 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
- if (mScanWifiLockTimer != null) {
+ if (mWifiScanTimer != null) {
out.writeInt(1);
- mScanWifiLockTimer.writeToParcel(out, batteryRealtime);
+ mWifiScanTimer.writeToParcel(out, batteryRealtime);
} else {
out.writeInt(0);
}
@@ -2954,12 +2987,12 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
mFullWifiLockTimer = null;
}
- mScanWifiLockOut = false;
+ mWifiScanStarted = false;
if (in.readInt() != 0) {
- mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK,
- mScanWifiLockTimers, mUnpluggables, in);
+ mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN,
+ mWifiScanTimers, mUnpluggables, in);
} else {
- mScanWifiLockTimer = null;
+ mWifiScanTimer = null;
}
mWifiMulticastEnabled = false;
if (in.readInt() != 0) {
@@ -5118,9 +5151,9 @@ public final class BatteryStatsImpl extends BatteryStats {
if (in.readInt() != 0) {
u.mFullWifiLockTimer.readSummaryFromParcelLocked(in);
}
- u.mScanWifiLockOut = false;
+ u.mWifiScanStarted = false;
if (in.readInt() != 0) {
- u.mScanWifiLockTimer.readSummaryFromParcelLocked(in);
+ u.mWifiScanTimer.readSummaryFromParcelLocked(in);
}
u.mWifiMulticastEnabled = false;
if (in.readInt() != 0) {
@@ -5310,9 +5343,9 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
- if (u.mScanWifiLockTimer != null) {
+ if (u.mWifiScanTimer != null) {
out.writeInt(1);
- u.mScanWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ u.mWifiScanTimer.writeSummaryFromParcelLocked(out, NOWREAL);
} else {
out.writeInt(0);
}
@@ -5537,7 +5570,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mWindowTimers.clear();
mWifiRunningTimers.clear();
mFullWifiLockTimers.clear();
- mScanWifiLockTimers.clear();
+ mWifiScanTimers.clear();
mWifiMulticastTimers.clear();
sNumSpeedSteps = in.readInt();
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index a94fb1e..84699dc 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -22,35 +22,14 @@ import android.os.Looper;
import android.os.Message;
public class HandlerCaller {
- private static final String TAG = "HandlerCaller";
- private static final boolean DEBUG = false;
-
+
public final Context mContext;
final Looper mMainLooper;
final Handler mH;
final Callback mCallback;
-
- public static class SomeArgs {
- SomeArgs next;
-
- public Object arg1;
- public Object arg2;
- public Object arg3;
- public Object arg4;
- public int argi1;
- public int argi2;
- public int argi3;
- public int argi4;
- public int argi5;
- public int argi6;
- }
-
- static final int ARGS_POOL_MAX_SIZE = 10;
- int mArgsPoolSize;
- SomeArgs mArgsPool;
-
+
class MyHandler extends Handler {
MyHandler(Looper looper) {
super(looper);
@@ -80,29 +59,6 @@ public class HandlerCaller {
mCallback = callback;
}
- public SomeArgs obtainArgs() {
- synchronized (mH) {
- SomeArgs args = mArgsPool;
- if (args != null) {
- mArgsPool = args.next;
- args.next = null;
- mArgsPoolSize--;
- return args;
- }
- }
- return new SomeArgs();
- }
-
- public void recycleArgs(SomeArgs args) {
- synchronized (mH) {
- if (mArgsPoolSize < ARGS_POOL_MAX_SIZE) {
- args.next = mArgsPool;
- mArgsPool = args;
- mArgsPoolSize++;
- }
- }
- }
-
public void executeOrSendMessage(Message msg) {
// If we are calling this from the main thread, then we can call
// right through. Otherwise, we need to send the message to the
@@ -141,7 +97,7 @@ public class HandlerCaller {
}
public Message obtainMessageBOO(int what, boolean arg1, Object arg2, Object arg3) {
- SomeArgs args = obtainArgs();
+ SomeArgs args = SomeArgs.obtain();
args.arg1 = arg2;
args.arg2 = arg3;
return mH.obtainMessage(what, arg1 ? 1 : 0, 0, args);
@@ -169,28 +125,28 @@ public class HandlerCaller {
public Message obtainMessageIIOO(int what, int arg1, int arg2,
Object arg3, Object arg4) {
- SomeArgs args = obtainArgs();
+ SomeArgs args = SomeArgs.obtain();
args.arg1 = arg3;
args.arg2 = arg4;
return mH.obtainMessage(what, arg1, arg2, args);
}
public Message obtainMessageIOO(int what, int arg1, Object arg2, Object arg3) {
- SomeArgs args = obtainArgs();
+ SomeArgs args = SomeArgs.obtain();
args.arg1 = arg2;
args.arg2 = arg3;
return mH.obtainMessage(what, arg1, 0, args);
}
public Message obtainMessageOO(int what, Object arg1, Object arg2) {
- SomeArgs args = obtainArgs();
+ SomeArgs args = SomeArgs.obtain();
args.arg1 = arg1;
args.arg2 = arg2;
return mH.obtainMessage(what, 0, 0, args);
}
public Message obtainMessageOOO(int what, Object arg1, Object arg2, Object arg3) {
- SomeArgs args = obtainArgs();
+ SomeArgs args = SomeArgs.obtain();
args.arg1 = arg1;
args.arg2 = arg2;
args.arg3 = arg3;
@@ -199,7 +155,7 @@ public class HandlerCaller {
public Message obtainMessageOOOO(int what, Object arg1, Object arg2,
Object arg3, Object arg4) {
- SomeArgs args = obtainArgs();
+ SomeArgs args = SomeArgs.obtain();
args.arg1 = arg1;
args.arg2 = arg2;
args.arg3 = arg3;
@@ -209,7 +165,7 @@ public class HandlerCaller {
public Message obtainMessageIIII(int what, int arg1, int arg2,
int arg3, int arg4) {
- SomeArgs args = obtainArgs();
+ SomeArgs args = SomeArgs.obtain();
args.argi1 = arg1;
args.argi2 = arg2;
args.argi3 = arg3;
@@ -219,7 +175,7 @@ public class HandlerCaller {
public Message obtainMessageIIIIII(int what, int arg1, int arg2,
int arg3, int arg4, int arg5, int arg6) {
- SomeArgs args = obtainArgs();
+ SomeArgs args = SomeArgs.obtain();
args.argi1 = arg1;
args.argi2 = arg2;
args.argi3 = arg3;
@@ -231,7 +187,7 @@ public class HandlerCaller {
public Message obtainMessageIIIIO(int what, int arg1, int arg2,
int arg3, int arg4, Object arg5) {
- SomeArgs args = obtainArgs();
+ SomeArgs args = SomeArgs.obtain();
args.arg1 = arg5;
args.argi1 = arg1;
args.argi2 = arg2;
diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java
new file mode 100644
index 0000000..88e58dc
--- /dev/null
+++ b/core/java/com/android/internal/os/SomeArgs.java
@@ -0,0 +1,95 @@
+/*
+ * 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 com.android.internal.os;
+
+/**
+ * Helper class for passing more arguments though a message
+ * and avoiding allocation of a custom class for wrapping the
+ * arguments. This class maintains a pool of instances and
+ * it is responsibility of the client to recycle and instance
+ * once it is no longer used.
+ */
+public final class SomeArgs {
+
+ private static final int MAX_POOL_SIZE = 10;
+
+ private static SomeArgs sPool;
+ private static int sPoolSize;
+ private static Object sPoolLock = new Object();
+
+ private SomeArgs mNext;
+
+ private boolean mInPool;
+
+ public Object arg1;
+ public Object arg2;
+ public Object arg3;
+ public Object arg4;
+ public int argi1;
+ public int argi2;
+ public int argi3;
+ public int argi4;
+ public int argi5;
+ public int argi6;
+
+ private SomeArgs() {
+ /* do nothing - reduce visibility */
+ }
+
+ public static SomeArgs obtain() {
+ synchronized (sPoolLock) {
+ if (sPoolSize > 0) {
+ SomeArgs args = sPool;
+ sPool = sPool.mNext;
+ args.mNext = null;
+ args.mInPool = false;
+ sPoolSize--;
+ return args;
+ } else {
+ return new SomeArgs();
+ }
+ }
+ }
+
+ public void recycle() {
+ if (mInPool) {
+ throw new IllegalStateException("Already recycled.");
+ }
+ synchronized (sPoolLock) {
+ clear();
+ if (sPoolSize < MAX_POOL_SIZE) {
+ mNext = sPool;
+ mInPool = true;
+ sPool = this;
+ sPoolSize++;
+ }
+ }
+ }
+
+ private void clear() {
+ arg1 = null;
+ arg2 = null;
+ arg3 = null;
+ arg4 = null;
+ argi1 = 0;
+ argi2 = 0;
+ argi3 = 0;
+ argi4 = 0;
+ argi5 = 0;
+ argi6 = 0;
+ }
+}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index b016e99..d24513a 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -18,16 +18,14 @@ package com.android.internal.os;
import android.net.Credentials;
import android.net.LocalSocket;
-import android.os.Build;
import android.os.Process;
+import android.os.SELinux;
import android.os.SystemProperties;
import android.util.Log;
import dalvik.system.PathClassLoader;
import dalvik.system.Zygote;
-import android.os.SELinux;
-
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -234,9 +232,9 @@ class ZygoteConnection {
ZygoteInit.setCloseOnExec(serverPipeFd, true);
}
- pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid,
- parsedArgs.gids, parsedArgs.debugFlags, rlimits,
- parsedArgs.seInfo, parsedArgs.niceName);
+ pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
+ parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
+ parsedArgs.niceName);
} catch (IOException ex) {
logAndPrintError(newStderr, "Exception creating pipe", ex);
} catch (ErrnoException ex) {
@@ -341,6 +339,9 @@ class ZygoteConnection {
*/
int debugFlags;
+ /** From --mount-external */
+ int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+
/** from --target-sdk-version. */
int targetSdkVersion;
boolean targetSdkVersionSpecified;
@@ -526,6 +527,10 @@ class ZygoteConnection {
"Duplicate arg specified");
}
niceName = arg.substring(arg.indexOf('=') + 1);
+ } else if (arg.equals("--mount-external-multiuser")) {
+ mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
+ } else if (arg.equals("--mount-external-multiuser-all")) {
+ mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL;
} else {
break;
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 4924326..9e43749 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -386,7 +386,12 @@ public class ZygoteInit {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
}
if (id != 0) {
- mResources.getColorStateList(id);
+ if (mResources.getColorStateList(id) == null) {
+ throw new IllegalArgumentException(
+ "Unable to find preloaded color resource #0x"
+ + Integer.toHexString(id)
+ + " (" + ar.getString(i) + ")");
+ }
}
}
return N;
@@ -409,11 +414,11 @@ public class ZygoteInit {
Log.v(TAG, "Preloading resource #" + Integer.toHexString(id));
}
if (id != 0) {
- Drawable dr = mResources.getDrawable(id);
- if ((dr.getChangingConfigurations()&~ActivityInfo.CONFIG_FONT_SCALE) != 0) {
- Log.w(TAG, "Preloaded drawable resource #0x"
+ if (mResources.getDrawable(id) == null) {
+ throw new IllegalArgumentException(
+ "Unable to find preloaded drawable resource #0x"
+ Integer.toHexString(id)
- + " (" + ar.getString(i) + ") that varies with configuration!!");
+ + " (" + ar.getString(i) + ")");
}
}
}
diff --git a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
index 3905c88..fb7f215 100644
--- a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
+++ b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
@@ -122,7 +122,7 @@ public class ExternalStorageFormatter extends Service
public void onCancel(DialogInterface dialog) {
IMountService mountService = getMountService();
String extStoragePath = mStorageVolume == null ?
- Environment.getExternalStorageDirectory().toString() :
+ Environment.getLegacyExternalStorageDirectory().toString() :
mStorageVolume.getPath();
try {
mountService.mountVolume(extStoragePath);
@@ -149,7 +149,7 @@ public class ExternalStorageFormatter extends Service
updateProgressDialog(R.string.progress_unmounting);
IMountService mountService = getMountService();
final String extStoragePath = mStorageVolume == null ?
- Environment.getExternalStorageDirectory().toString() :
+ Environment.getLegacyExternalStorageDirectory().toString() :
mStorageVolume.getPath();
try {
// Remove encryption mapping if this is an unmount for a factory reset.
@@ -163,7 +163,7 @@ public class ExternalStorageFormatter extends Service
updateProgressDialog(R.string.progress_erasing);
final IMountService mountService = getMountService();
final String extStoragePath = mStorageVolume == null ?
- Environment.getExternalStorageDirectory().toString() :
+ Environment.getLegacyExternalStorageDirectory().toString() :
mStorageVolume.getPath();
if (mountService != null) {
new Thread() {
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 294d4c4..780f5b3 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -28,8 +28,9 @@ oneway interface IStatusBar
void updateNotification(IBinder key, in StatusBarNotification notification);
void removeNotification(IBinder key);
void disable(int state);
- void animateExpand();
- void animateCollapse();
+ void animateExpandNotificationsPanel();
+ void animateExpandSettingsPanel();
+ void animateCollapsePanels();
void setSystemUiVisibility(int vis, int mask);
void topAppWindowChanged(boolean menuVisible);
void setImeWindowStatus(in IBinder token, int vis, int backDisposition);
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index c64f170..04e5bc9 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -24,14 +24,16 @@ import com.android.internal.statusbar.StatusBarNotification;
/** @hide */
interface IStatusBarService
{
- void expand();
- void collapse();
+ void expandNotificationsPanel();
+ void collapsePanels();
void disable(int what, IBinder token, String pkg);
void setIcon(String slot, String iconPackage, int iconId, int iconLevel, String contentDescription);
void setIconVisibility(String slot, boolean visible);
void removeIcon(String slot);
void topAppWindowChanged(boolean menuVisible);
void setImeWindowStatus(in IBinder token, int vis, int backDisposition);
+ void expandSettingsPanel();
+ void setCurrentUser(int newUserId);
// ---- Methods below are for use by the status bar policy services ----
// You need the STATUS_BAR_SERVICE permission
diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java
index 3333c82..e0792cb 100644
--- a/core/java/com/android/internal/statusbar/StatusBarIcon.java
+++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java
@@ -18,18 +18,21 @@ package com.android.internal.statusbar;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
public class StatusBarIcon implements Parcelable {
public String iconPackage;
+ public UserHandle user;
public int iconId;
public int iconLevel;
public boolean visible = true;
public int number;
public CharSequence contentDescription;
- public StatusBarIcon(String iconPackage, int iconId, int iconLevel, int number,
+ public StatusBarIcon(String iconPackage, UserHandle user, int iconId, int iconLevel, int number,
CharSequence contentDescription) {
this.iconPackage = iconPackage;
+ this.user = user;
this.iconId = iconId;
this.iconLevel = iconLevel;
this.number = number;
@@ -38,15 +41,16 @@ public class StatusBarIcon implements Parcelable {
@Override
public String toString() {
- return "StatusBarIcon(pkg=" + this.iconPackage + " id=0x" + Integer.toHexString(this.iconId)
+ return "StatusBarIcon(pkg=" + this.iconPackage + "user=" + user.getIdentifier()
+ + " id=0x" + Integer.toHexString(this.iconId)
+ " level=" + this.iconLevel + " visible=" + visible
+ " num=" + this.number + " )";
}
@Override
public StatusBarIcon clone() {
- StatusBarIcon that = new StatusBarIcon(this.iconPackage, this.iconId, this.iconLevel,
- this.number, this.contentDescription);
+ StatusBarIcon that = new StatusBarIcon(this.iconPackage, this.user, this.iconId,
+ this.iconLevel, this.number, this.contentDescription);
that.visible = this.visible;
return that;
}
@@ -60,6 +64,7 @@ public class StatusBarIcon implements Parcelable {
public void readFromParcel(Parcel in) {
this.iconPackage = in.readString();
+ this.user = (UserHandle) in.readParcelable(null);
this.iconId = in.readInt();
this.iconLevel = in.readInt();
this.visible = in.readInt() != 0;
@@ -69,6 +74,7 @@ public class StatusBarIcon implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
out.writeString(this.iconPackage);
+ out.writeParcelable(this.user, 0);
out.writeInt(this.iconId);
out.writeInt(this.iconLevel);
out.writeInt(this.visible ? 1 : 0);
diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.java b/core/java/com/android/internal/statusbar/StatusBarNotification.java
index d443523..a91aa3c 100644
--- a/core/java/com/android/internal/statusbar/StatusBarNotification.java
+++ b/core/java/com/android/internal/statusbar/StatusBarNotification.java
@@ -19,8 +19,7 @@ package com.android.internal.statusbar;
import android.app.Notification;
import android.os.Parcel;
import android.os.Parcelable;
-import android.widget.RemoteViews;
-
+import android.os.UserHandle;
/*
boolean clearable = !n.ongoingEvent && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0);
@@ -38,19 +37,26 @@ if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) {
* Class encapsulating a Notification. Sent by the NotificationManagerService to the IStatusBar (in System UI).
*/
public class StatusBarNotification implements Parcelable {
- public String pkg;
- public int id;
- public String tag;
- public int uid;
- public int initialPid;
- public Notification notification;
- public int score;
-
- public StatusBarNotification() {
+ public final String pkg;
+ public final int id;
+ public final String tag;
+ public final int uid;
+ public final int initialPid;
+ // TODO: make this field private and move callers to an accessor that
+ // ensures sourceUser is applied.
+ public final Notification notification;
+ public final int score;
+ public final UserHandle user;
+
+ /** This is temporarily needed for the JB MR1 PDK. */
+ @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);
}
- public StatusBarNotification(String pkg, int id, String tag,
- int uid, int initialPid, int score, Notification notification) {
+ public StatusBarNotification(String pkg, int id, String tag, int uid, int initialPid, int score,
+ Notification notification, UserHandle user) {
if (pkg == null) throw new NullPointerException();
if (notification == null) throw new NullPointerException();
@@ -61,13 +67,11 @@ public class StatusBarNotification implements Parcelable {
this.initialPid = initialPid;
this.score = score;
this.notification = notification;
+ this.user = user;
+ this.notification.setUser(user);
}
public StatusBarNotification(Parcel in) {
- readFromParcel(in);
- }
-
- public void readFromParcel(Parcel in) {
this.pkg = in.readString();
this.id = in.readInt();
if (in.readInt() != 0) {
@@ -79,6 +83,8 @@ public class StatusBarNotification implements Parcelable {
this.initialPid = in.readInt();
this.score = in.readInt();
this.notification = new Notification(in);
+ this.user = UserHandle.readFromParcel(in);
+ this.notification.setUser(user);
}
public void writeToParcel(Parcel out, int flags) {
@@ -94,6 +100,7 @@ public class StatusBarNotification implements Parcelable {
out.writeInt(this.initialPid);
out.writeInt(this.score);
this.notification.writeToParcel(out, flags);
+ user.writeToParcel(out, flags);
}
public int describeContents() {
@@ -114,14 +121,16 @@ public class StatusBarNotification implements Parcelable {
}
};
+ @Override
public StatusBarNotification clone() {
- return new StatusBarNotification(this.pkg, this.id, this.tag,
- this.uid, this.initialPid, this.score, this.notification.clone());
+ return new StatusBarNotification(this.pkg, this.id, this.tag, this.uid, this.initialPid,
+ this.score, this.notification.clone(), this.user);
}
+ @Override
public String toString() {
- return "StatusBarNotification(pkg=" + pkg + " id=" + id + " tag=" + tag
- + " score=" + score + " notn=" + notification + ")";
+ return "StatusBarNotification(pkg=" + pkg + " id=" + id + " tag=" + tag + " score=" + score
+ + " notn=" + notification + " user=" + user + ")";
}
public boolean isOngoing() {
@@ -132,6 +141,9 @@ public class StatusBarNotification implements Parcelable {
return ((notification.flags & Notification.FLAG_ONGOING_EVENT) == 0)
&& ((notification.flags & Notification.FLAG_NO_CLEAR) == 0);
}
-}
-
+ /** Returns a userHandle for the instance of the app that posted this notification. */
+ public int getUserId() {
+ return this.user.getIdentifier();
+ }
+}
diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java
new file mode 100644
index 0000000..7b8c582
--- /dev/null
+++ b/core/java/com/android/internal/util/DumpUtils.java
@@ -0,0 +1,57 @@
+/*
+ * 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 com.android.internal.util;
+
+import android.os.Handler;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Helper functions for dumping the state of system services.
+ */
+public final class DumpUtils {
+ private DumpUtils() {
+ }
+
+ /**
+ * Helper for dumping state owned by a handler thread.
+ *
+ * Because the caller might be holding an important lock that the handler is
+ * trying to acquire, we use a short timeout to avoid deadlocks. The process
+ * is inelegant but this function is only used for debugging purposes.
+ */
+ public static void dumpAsync(Handler handler, final Dump dump, PrintWriter pw, long timeout) {
+ final StringWriter sw = new StringWriter();
+ if (handler.runWithScissors(new Runnable() {
+ @Override
+ public void run() {
+ PrintWriter lpw = new PrintWriter(sw);
+ dump.dump(lpw);
+ lpw.close();
+ }
+ }, timeout)) {
+ pw.print(sw.toString());
+ } else {
+ pw.println("... timed out");
+ }
+ }
+
+ public interface Dump {
+ void dump(PrintWriter pw);
+ }
+}
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index 699e9b3..dd5918b 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -28,7 +28,7 @@ public class IndentingPrintWriter extends PrintWriter {
private final String mIndent;
private StringBuilder mBuilder = new StringBuilder();
- private String mCurrent = new String();
+ private char[] mCurrent;
private boolean mEmptyLine = true;
public IndentingPrintWriter(Writer writer, String indent) {
@@ -38,12 +38,12 @@ public class IndentingPrintWriter extends PrintWriter {
public void increaseIndent() {
mBuilder.append(mIndent);
- mCurrent = mBuilder.toString();
+ mCurrent = null;
}
public void decreaseIndent() {
mBuilder.delete(0, mIndent.length());
- mCurrent = mBuilder.toString();
+ mCurrent = null;
}
public void printPair(String key, Object value) {
@@ -51,17 +51,35 @@ public class IndentingPrintWriter extends PrintWriter {
}
@Override
- public void println() {
- super.println();
- mEmptyLine = true;
+ public void write(char[] buf, int offset, int count) {
+ final int bufferEnd = offset + count;
+ int lineStart = offset;
+ int lineEnd = offset;
+ while (lineEnd < bufferEnd) {
+ char ch = buf[lineEnd++];
+ if (ch == '\n') {
+ writeIndent();
+ super.write(buf, lineStart, lineEnd - lineStart);
+ lineStart = lineEnd;
+ mEmptyLine = true;
+ }
+ }
+
+ if (lineStart != lineEnd) {
+ writeIndent();
+ super.write(buf, lineStart, lineEnd - lineStart);
+ }
}
- @Override
- public void write(char[] buf, int offset, int count) {
+ private void writeIndent() {
if (mEmptyLine) {
mEmptyLine = false;
- super.print(mCurrent);
+ if (mBuilder.length() != 0) {
+ if (mCurrent == null) {
+ mCurrent = mBuilder.toString().toCharArray();
+ }
+ super.write(mCurrent, 0, mCurrent.length);
+ }
}
- super.write(buf, offset, count);
}
}
diff --git a/core/java/com/android/internal/util/JournaledFile.java b/core/java/com/android/internal/util/JournaledFile.java
index af0c6c6..eeffc16 100644
--- a/core/java/com/android/internal/util/JournaledFile.java
+++ b/core/java/com/android/internal/util/JournaledFile.java
@@ -19,6 +19,15 @@ package com.android.internal.util;
import java.io.File;
import java.io.IOException;
+/**
+ * @Deprecated Use {@link com.android.internal.os.AtomicFile} instead. It would
+ * be nice to update all existing uses of this to switch to AtomicFile, but since
+ * their on-file semantics are slightly different that would run the risk of losing
+ * data if at the point of the platform upgrade to the new code it would need to
+ * roll back to the backup file. This can be solved... but is it worth it and
+ * all of the testing needed to make sure it is correct?
+ */
+@Deprecated
public class JournaledFile {
File mReal;
File mTemp;
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index a53a9c0..a327adc 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -54,4 +54,16 @@ public class Preconditions {
return reference;
}
+ /**
+ * Ensures the truth of an expression involving the state of the calling
+ * instance, but not involving any parameters to the calling method.
+ *
+ * @param expression a boolean expression
+ * @throws IllegalStateException if {@code expression} is false
+ */
+ public static void checkState(boolean expression) {
+ if (!expression) {
+ throw new IllegalStateException();
+ }
+ }
}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 4c34d73..b76e89d 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -28,12 +28,13 @@ import android.view.IWindowSession;
public class BaseIWindow extends IWindow.Stub {
private IWindowSession mSession;
public int mSeq;
-
+
public void setSession(IWindowSession session) {
mSession = session;
}
-
- public void resized(int w, int h, Rect contentInsets,
+
+ @Override
+ public void resized(Rect frame, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
if (reportDraw) {
try {
@@ -43,24 +44,35 @@ public class BaseIWindow extends IWindow.Stub {
}
}
+ @Override
+ public void moved(int newX, int newY) {
+ }
+
+ @Override
public void dispatchAppVisibility(boolean visible) {
}
+ @Override
public void dispatchGetNewSurface() {
}
+ @Override
public void dispatchScreenState(boolean on) {
}
+ @Override
public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
}
+ @Override
public void executeCommand(String command, String parameters, ParcelFileDescriptor out) {
}
-
+
+ @Override
public void closeSystemDialogs(String reason) {
}
-
+
+ @Override
public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, boolean sync) {
if (sync) {
try {
@@ -70,14 +82,17 @@ public class BaseIWindow extends IWindow.Stub {
}
}
+ @Override
public void dispatchDragEvent(DragEvent event) {
}
+ @Override
public void dispatchSystemUiVisibilityChanged(int seq, int globalUi,
int localValue, int localChanges) {
mSeq = seq;
}
+ @Override
public void dispatchWallpaperCommand(String action, int x, int y,
int z, Bundle extras, boolean sync) {
if (sync) {
@@ -88,6 +103,7 @@ public class BaseIWindow extends IWindow.Stub {
}
}
+ @Override
public void doneAnimating() {
}
}
diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl
index f875cbd..cdec254 100644
--- a/core/java/com/android/internal/view/IInputMethodSession.aidl
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -47,6 +47,8 @@ oneway interface IInputMethodSession {
void dispatchTrackballEvent(int seq, in MotionEvent event, IInputMethodCallback callback);
+ void dispatchGenericMotionEvent(int seq, in MotionEvent event, IInputMethodCallback callback);
+
void appPrivateCommand(String action, in Bundle data);
void toggleSoftInput(int showFlags, int hideFlags);
diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java
index af512a3..95130c8 100644
--- a/core/java/com/android/internal/view/RotationPolicy.java
+++ b/core/java/com/android/internal/view/RotationPolicy.java
@@ -23,10 +23,12 @@ import android.os.AsyncTask;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import android.view.IWindowManager;
import android.view.Surface;
+import android.view.WindowManagerGlobal;
/**
* Provides helper functions for configuring the display rotation policy.
@@ -54,16 +56,17 @@ public final class RotationPolicy {
*/
public static boolean isRotationLockToggleVisible(Context context) {
return isRotationLockToggleSupported(context) &&
- Settings.System.getInt(context.getContentResolver(),
- Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0) == 0;
+ Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0,
+ UserHandle.USER_CURRENT) == 0;
}
/**
* Returns true if rotation lock is enabled.
*/
public static boolean isRotationLocked(Context context) {
- return Settings.System.getInt(context.getContentResolver(),
- Settings.System.ACCELEROMETER_ROTATION, 0) == 0;
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT) == 0;
}
/**
@@ -72,15 +75,15 @@ public final class RotationPolicy {
* Should be used by the rotation lock toggle.
*/
public static void setRotationLock(Context context, final boolean enabled) {
- Settings.System.putInt(context.getContentResolver(),
- Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0);
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0,
+ UserHandle.USER_CURRENT);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
try {
- IWindowManager wm = IWindowManager.Stub.asInterface(
- ServiceManager.getService(Context.WINDOW_SERVICE));
+ IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
if (enabled) {
wm.freezeRotation(-1);
} else {
@@ -100,15 +103,15 @@ public final class RotationPolicy {
* Should be used by Display settings and Accessibility settings.
*/
public static void setRotationLockForAccessibility(Context context, final boolean enabled) {
- Settings.System.putInt(context.getContentResolver(),
- Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, enabled ? 1 : 0);
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, enabled ? 1 : 0,
+ UserHandle.USER_CURRENT);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
try {
- IWindowManager wm = IWindowManager.Stub.asInterface(
- ServiceManager.getService(Context.WINDOW_SERVICE));
+ IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
if (enabled) {
wm.freezeRotation(Surface.ROTATION_0);
} else {
@@ -122,16 +125,25 @@ public final class RotationPolicy {
}
/**
- * Registers a listener for rotation policy changes.
+ * Registers a listener for rotation policy changes affecting the caller's user
*/
public static void registerRotationPolicyListener(Context context,
RotationPolicyListener listener) {
+ registerRotationPolicyListener(context, listener, UserHandle.getCallingUserId());
+ }
+
+ /**
+ * Registers a listener for rotation policy changes affecting a specific user,
+ * or USER_ALL for all users.
+ */
+ public static void registerRotationPolicyListener(Context context,
+ RotationPolicyListener listener, int userHandle) {
context.getContentResolver().registerContentObserver(Settings.System.getUriFor(
Settings.System.ACCELEROMETER_ROTATION),
- false, listener.mObserver);
+ false, listener.mObserver, userHandle);
context.getContentResolver().registerContentObserver(Settings.System.getUriFor(
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY),
- false, listener.mObserver);
+ false, listener.mObserver, userHandle);
}
/**
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 449194b..238a9c0 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -17,6 +17,7 @@
package com.android.internal.view.menu;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
@@ -48,6 +49,9 @@ public class ActionMenuItemView extends TextView
private int mMinWidth;
private int mSavedPaddingLeft;
+ private static final int MAX_ICON_SIZE = 32; // dp
+ private int mMaxIconSize;
+
public ActionMenuItemView(Context context) {
this(context, null);
}
@@ -67,6 +71,9 @@ public class ActionMenuItemView extends TextView
com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0);
a.recycle();
+ final float density = res.getDisplayMetrics().density;
+ mMaxIconSize = (int) (MAX_ICON_SIZE * density + 0.5f);
+
setOnClickListener(this);
setOnLongClickListener(this);
@@ -74,6 +81,15 @@ public class ActionMenuItemView extends TextView
}
@Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ mAllowTextWithIcon = getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
+ updateTextButtonVisibility();
+ }
+
+ @Override
public void setPadding(int l, int t, int r, int b) {
mSavedPaddingLeft = l;
super.setPadding(l, t, r, b);
@@ -135,7 +151,22 @@ public class ActionMenuItemView extends TextView
public void setIcon(Drawable icon) {
mIcon = icon;
- setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
+ if (icon != null) {
+ int width = icon.getIntrinsicWidth();
+ int height = icon.getIntrinsicHeight();
+ if (width > mMaxIconSize) {
+ final float scale = (float) mMaxIconSize / width;
+ width = mMaxIconSize;
+ height *= scale;
+ }
+ if (height > mMaxIconSize) {
+ final float scale = (float) mMaxIconSize / height;
+ height = mMaxIconSize;
+ width *= scale;
+ }
+ icon.setBounds(0, 0, width, height);
+ }
+ setCompoundDrawables(icon, null, null, null);
updateTextButtonVisibility();
}
@@ -209,7 +240,7 @@ public class ActionMenuItemView extends TextView
Toast cheatSheet = Toast.makeText(context, mItemData.getTitle(), Toast.LENGTH_SHORT);
if (midy < displayFrame.height()) {
// Show along the top; follow action buttons
- cheatSheet.setGravity(Gravity.TOP | Gravity.RIGHT,
+ cheatSheet.setGravity(Gravity.TOP | Gravity.END,
screenWidth - screenPos[0] - width / 2, height);
} else {
// Show along the bottom center
@@ -221,6 +252,11 @@ public class ActionMenuItemView extends TextView
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
+ // Fill all available height.
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
+ }
final boolean textVisible = hasText();
if (textVisible && mSavedPaddingLeft >= 0) {
super.setPadding(mSavedPaddingLeft, getPaddingTop(),
@@ -245,7 +281,7 @@ public class ActionMenuItemView extends TextView
// TextView won't center compound drawables in both dimensions without
// a little coercion. Pad in to center the icon after we've measured.
final int w = getMeasuredWidth();
- final int dw = mIcon.getIntrinsicWidth();
+ final int dw = mIcon.getBounds().width();
super.setPadding((w - dw) / 2, getPaddingTop(), getPaddingRight(), getPaddingBottom());
}
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
index cf6029e..2811332 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
@@ -576,6 +576,16 @@ public class ActionMenuPresenter extends BaseMenuPresenter
public boolean needsDividerAfter() {
return false;
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
+ // Fill available height
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
}
private class OverflowPopup extends MenuPopupHelper {
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java
index cef6a8f..34ade74 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -15,6 +15,8 @@
*/
package com.android.internal.view.menu;
+import com.android.internal.R;
+
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -26,8 +28,6 @@ import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.LinearLayout;
-import com.android.internal.R;
-
/**
* @hide
*/
@@ -73,6 +73,11 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
return mFormatItems;
}
+ public void setMaxItemHeight(int maxItemHeight) {
+ mMaxItemHeight = maxItemHeight;
+ requestLayout();
+ }
+
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -395,6 +400,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
int nonOverflowCount = 0;
int widthRemaining = right - left - getPaddingRight() - getPaddingLeft();
boolean hasOverflow = false;
+ final boolean isLayoutRtl = isLayoutRtl();
for (int i = 0; i < childCount; i++) {
final View v = getChildAt(i);
if (v.getVisibility() == GONE) {
@@ -409,8 +415,15 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
}
int height = v.getMeasuredHeight();
- int r = getWidth() - getPaddingRight() - p.rightMargin;
- int l = r - overflowWidth;
+ int r;
+ int l;
+ if (isLayoutRtl) {
+ l = getPaddingLeft() + p.leftMargin;
+ r = l + overflowWidth;
+ } else {
+ r = getWidth() - getPaddingRight() - p.rightMargin;
+ l = r - overflowWidth;
+ }
int t = midVertical - (height / 2);
int b = t + height;
v.layout(l, t, r, b);
@@ -443,20 +456,38 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
final int spacerCount = nonOverflowCount - (hasOverflow ? 0 : 1);
final int spacerSize = Math.max(0, spacerCount > 0 ? widthRemaining / spacerCount : 0);
- int startLeft = getPaddingLeft();
- for (int i = 0; i < childCount; i++) {
- final View v = getChildAt(i);
- final LayoutParams lp = (LayoutParams) v.getLayoutParams();
- if (v.getVisibility() == GONE || lp.isOverflowButton) {
- continue;
+ if (isLayoutRtl) {
+ int startRight = getWidth() - getPaddingRight();
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+ if (v.getVisibility() == GONE || lp.isOverflowButton) {
+ continue;
+ }
+
+ startRight -= lp.rightMargin;
+ int width = v.getMeasuredWidth();
+ int height = v.getMeasuredHeight();
+ int t = midVertical - height / 2;
+ v.layout(startRight - width, t, startRight, t + height);
+ startRight -= width + lp.leftMargin + spacerSize;
}
+ } else {
+ int startLeft = getPaddingLeft();
+ for (int i = 0; i < childCount; i++) {
+ final View v = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+ if (v.getVisibility() == GONE || lp.isOverflowButton) {
+ continue;
+ }
- startLeft += lp.leftMargin;
- int width = v.getMeasuredWidth();
- int height = v.getMeasuredHeight();
- int t = midVertical - height / 2;
- v.layout(startLeft, t, startLeft + width, t + height);
- startLeft += width + lp.rightMargin + spacerSize;
+ startLeft += lp.leftMargin;
+ int width = v.getMeasuredWidth();
+ int height = v.getMeasuredHeight();
+ int t = midVertical - height / 2;
+ v.layout(startLeft, t, startLeft + width, t + height);
+ startLeft += width + lp.rightMargin + spacerSize;
+ }
}
}
@@ -489,8 +520,10 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- if (p instanceof LayoutParams) {
- LayoutParams result = new LayoutParams((LayoutParams) p);
+ if (p != null) {
+ final LayoutParams result = p instanceof LayoutParams
+ ? new LayoutParams((LayoutParams) p)
+ : new LayoutParams(p);
if (result.gravity <= Gravity.NO_GRAVITY) {
result.gravity = Gravity.CENTER_VERTICAL;
}
@@ -566,6 +599,10 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
super(c, attrs);
}
+ public LayoutParams(ViewGroup.LayoutParams other) {
+ super(other);
+ }
+
public LayoutParams(LayoutParams other) {
super((LinearLayout.LayoutParams) other);
isOverflowButton = other.isOverflowButton;
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index a743cfa..5d0b25f 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -281,8 +281,8 @@ public final class IconMenuItemView extends TextView implements MenuView.ItemVie
Rect tmpRect = mPositionIconOutput;
getLineBounds(0, tmpRect);
mPositionIconAvailable.set(0, 0, getWidth(), tmpRect.top);
- final int layoutDirection = getResolvedLayoutDirection();
- Gravity.apply(Gravity.CENTER_VERTICAL | Gravity.LEFT, mIcon.getIntrinsicWidth(), mIcon
+ final int layoutDirection = getLayoutDirection();
+ Gravity.apply(Gravity.CENTER_VERTICAL | Gravity.START, mIcon.getIntrinsicWidth(), mIcon
.getIntrinsicHeight(), mPositionIconAvailable, mPositionIconOutput,
layoutDirection);
mIcon.setBounds(mPositionIconOutput);
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
index 25a9c54..ca7f5d0 100644
--- a/core/java/com/android/internal/widget/AbsActionBarView.java
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -95,6 +95,9 @@ public abstract class AbsActionBarView extends ViewGroup {
public void setContentHeight(int height) {
mContentHeight = height;
+ if (mMenuView != null) {
+ mMenuView.setMaxItemHeight(mContentHeight);
+ }
requestLayout();
}
@@ -219,24 +222,22 @@ public abstract class AbsActionBarView extends ViewGroup {
return Math.max(0, availableWidth);
}
- protected int positionChild(View child, int x, int y, int contentHeight) {
- int childWidth = child.getMeasuredWidth();
- int childHeight = child.getMeasuredHeight();
- int childTop = y + (contentHeight - childHeight) / 2;
-
- child.layout(x, childTop, x + childWidth, childTop + childHeight);
-
- return childWidth;
+ static protected int next(int x, int val, boolean isRtl) {
+ return isRtl ? x - val : x + val;
}
- protected int positionChildInverse(View child, int x, int y, int contentHeight) {
+ protected int positionChild(View child, int x, int y, int contentHeight, boolean reverse) {
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int childTop = y + (contentHeight - childHeight) / 2;
- child.layout(x - childWidth, childTop, x, childTop + childHeight);
+ if (reverse) {
+ child.layout(x - childWidth, childTop, x, childTop + childHeight);
+ } else {
+ child.layout(x, childTop, x + childWidth, childTop + childHeight);
+ }
- return childWidth;
+ return (reverse ? -childWidth : childWidth);
}
protected class VisibilityAnimListener implements Animator.AnimatorListener {
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index 0c572705..0cfe4fd 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -77,20 +77,107 @@ public class ActionBarContainer extends FrameLayout {
}
public void setPrimaryBackground(Drawable bg) {
+ if (mBackground != null) {
+ mBackground.setCallback(null);
+ unscheduleDrawable(mBackground);
+ }
mBackground = bg;
+ if (bg != null) {
+ bg.setCallback(this);
+ }
+ setWillNotDraw(mIsSplit ? mSplitBackground == null :
+ mBackground == null && mStackedBackground == null);
invalidate();
}
public void setStackedBackground(Drawable bg) {
+ if (mStackedBackground != null) {
+ mStackedBackground.setCallback(null);
+ unscheduleDrawable(mStackedBackground);
+ }
mStackedBackground = bg;
+ if (bg != null) {
+ bg.setCallback(this);
+ }
+ setWillNotDraw(mIsSplit ? mSplitBackground == null :
+ mBackground == null && mStackedBackground == null);
invalidate();
}
public void setSplitBackground(Drawable bg) {
+ if (mSplitBackground != null) {
+ mSplitBackground.setCallback(null);
+ unscheduleDrawable(mSplitBackground);
+ }
mSplitBackground = bg;
+ if (bg != null) {
+ bg.setCallback(this);
+ }
+ setWillNotDraw(mIsSplit ? mSplitBackground == null :
+ mBackground == null && mStackedBackground == null);
invalidate();
}
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+ final boolean isVisible = visibility == VISIBLE;
+ if (mBackground != null) mBackground.setVisible(isVisible, false);
+ if (mStackedBackground != null) mStackedBackground.setVisible(isVisible, false);
+ if (mSplitBackground != null) mSplitBackground.setVisible(isVisible, false);
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return (who == mBackground && !mIsSplit) || (who == mStackedBackground && mIsStacked) ||
+ (who == mSplitBackground && mIsSplit) || super.verifyDrawable(who);
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ if (mBackground != null && mBackground.isStateful()) {
+ mBackground.setState(getDrawableState());
+ }
+ if (mStackedBackground != null && mStackedBackground.isStateful()) {
+ mStackedBackground.setState(getDrawableState());
+ }
+ if (mSplitBackground != null && mSplitBackground.isStateful()) {
+ mSplitBackground.setState(getDrawableState());
+ }
+ }
+
+ @Override
+ public void jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState();
+ if (mBackground != null) {
+ mBackground.jumpToCurrentState();
+ }
+ if (mStackedBackground != null) {
+ mStackedBackground.jumpToCurrentState();
+ }
+ if (mSplitBackground != null) {
+ mSplitBackground.jumpToCurrentState();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onResolveDrawables(int layoutDirection) {
+ super.onResolveDrawables(layoutDirection);
+ if (mBackground != null) {
+ mBackground.setLayoutDirection(layoutDirection);
+ }
+ if (mStackedBackground != null) {
+ mStackedBackground.setLayoutDirection(layoutDirection);
+ }
+ if (mSplitBackground != null) {
+ mSplitBackground.setLayoutDirection(layoutDirection);
+ }
+ }
+
/**
* Set the action bar into a "transitioning" state. While transitioning
* the bar will block focus and touch from all of its descendants. This
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 16f08f5..8bc1081 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -454,15 +454,18 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int x = getPaddingLeft();
+ final boolean isLayoutRtl = isLayoutRtl();
+ int x = isLayoutRtl ? r - l - getPaddingRight() : getPaddingLeft();
final int y = getPaddingTop();
final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
if (mClose != null && mClose.getVisibility() != GONE) {
MarginLayoutParams lp = (MarginLayoutParams) mClose.getLayoutParams();
- x += lp.leftMargin;
- x += positionChild(mClose, x, y, contentHeight);
- x += lp.rightMargin;
+ final int startMargin = (isLayoutRtl ? lp.rightMargin : lp.leftMargin);
+ final int endMargin = (isLayoutRtl ? lp.leftMargin : lp.rightMargin);
+ x = next(x, startMargin, isLayoutRtl);
+ x += positionChild(mClose, x, y, contentHeight, isLayoutRtl);
+ x = next(x, endMargin, isLayoutRtl);
if (mAnimateInOnLayout) {
mAnimationMode = ANIMATE_IN;
@@ -473,17 +476,17 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi
}
if (mTitleLayout != null && mCustomView == null && mTitleLayout.getVisibility() != GONE) {
- x += positionChild(mTitleLayout, x, y, contentHeight);
+ x += positionChild(mTitleLayout, x, y, contentHeight, isLayoutRtl);
}
if (mCustomView != null) {
- x += positionChild(mCustomView, x, y, contentHeight);
+ x += positionChild(mCustomView, x, y, contentHeight, isLayoutRtl);
}
-
- x = r - l - getPaddingRight();
+
+ x = isLayoutRtl ? getPaddingLeft() : r - l - getPaddingRight();
if (mMenuView != null) {
- x -= positionChildInverse(mMenuView, x, y, contentHeight);
+ x += positionChild(mMenuView, x, y, contentHeight, !isLayoutRtl);
}
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 450f418..991c699 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -26,6 +26,7 @@ import com.android.internal.view.menu.MenuPresenter;
import com.android.internal.view.menu.MenuView;
import com.android.internal.view.menu.SubMenuBuilder;
+import android.animation.LayoutTransition;
import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener;
import android.app.Activity;
@@ -84,7 +85,7 @@ public class ActionBarView extends AbsActionBarView {
ActionBar.DISPLAY_SHOW_CUSTOM |
ActionBar.DISPLAY_SHOW_TITLE;
- private static final int DEFAULT_CUSTOM_GRAVITY = Gravity.LEFT | Gravity.CENTER_VERTICAL;
+ private static final int DEFAULT_CUSTOM_GRAVITY = Gravity.START | Gravity.CENTER_VERTICAL;
private int mNavigationMode;
private int mDisplayOptions = -1;
@@ -555,12 +556,16 @@ public class ActionBarView extends AbsActionBarView {
// Make sure the home button has an accurate content description for accessibility.
if (!enable) {
mHomeLayout.setContentDescription(null);
- } else if ((mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
- mHomeLayout.setContentDescription(mContext.getResources().getText(
- R.string.action_bar_up_description));
+ mHomeLayout.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
} else {
- mHomeLayout.setContentDescription(mContext.getResources().getText(
- R.string.action_bar_home_description));
+ mHomeLayout.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ if ((mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+ mHomeLayout.setContentDescription(mContext.getResources().getText(
+ R.string.action_bar_up_description));
+ } else {
+ mHomeLayout.setContentDescription(mContext.getResources().getText(
+ R.string.action_bar_home_description));
+ }
}
}
@@ -623,12 +628,16 @@ public class ActionBarView extends AbsActionBarView {
// Make sure the home button has an accurate content description for accessibility.
if (!mHomeLayout.isEnabled()) {
mHomeLayout.setContentDescription(null);
- } else if ((options & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
- mHomeLayout.setContentDescription(mContext.getResources().getText(
- R.string.action_bar_up_description));
+ mHomeLayout.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
} else {
- mHomeLayout.setContentDescription(mContext.getResources().getText(
- R.string.action_bar_home_description));
+ mHomeLayout.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ if ((options & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+ mHomeLayout.setContentDescription(mContext.getResources().getText(
+ R.string.action_bar_up_description));
+ } else {
+ mHomeLayout.setContentDescription(mContext.getResources().getText(
+ R.string.action_bar_home_description));
+ }
}
}
@@ -847,7 +856,7 @@ public class ActionBarView extends AbsActionBarView {
int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
- int maxHeight = mContentHeight > 0 ?
+ int maxHeight = mContentHeight >= 0 ?
mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
final int verticalPadding = getPaddingTop() + getPaddingBottom();
@@ -855,6 +864,7 @@ public class ActionBarView extends AbsActionBarView {
final int paddingRight = getPaddingRight();
final int height = maxHeight - verticalPadding;
final int childSpecHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST);
+ final int exactHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
int availableWidth = contentWidth - paddingLeft - paddingRight;
int leftOfCenter = availableWidth / 2;
@@ -870,16 +880,14 @@ public class ActionBarView extends AbsActionBarView {
} else {
homeWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
}
- homeLayout.measure(homeWidthSpec,
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
- final int homeWidth = homeLayout.getMeasuredWidth() + homeLayout.getLeftOffset();
+ homeLayout.measure(homeWidthSpec, exactHeightSpec);
+ final int homeWidth = homeLayout.getMeasuredWidth() + homeLayout.getStartOffset();
availableWidth = Math.max(0, availableWidth - homeWidth);
leftOfCenter = Math.max(0, availableWidth - homeWidth);
}
if (mMenuView != null && mMenuView.getParent() == this) {
- availableWidth = measureChildView(mMenuView, availableWidth,
- childSpecHeight, 0);
+ availableWidth = measureChildView(mMenuView, availableWidth, exactHeightSpec, 0);
rightOfCenter = Math.max(0, rightOfCenter - mMenuView.getMeasuredWidth());
}
@@ -1010,8 +1018,6 @@ public class ActionBarView extends AbsActionBarView {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int x = getPaddingLeft();
- final int y = getPaddingTop();
final int contentHeight = b - t - getPaddingTop() - getPaddingBottom();
if (contentHeight <= 0) {
@@ -1019,13 +1025,23 @@ public class ActionBarView extends AbsActionBarView {
return;
}
+ final boolean isLayoutRtl = isLayoutRtl();
+ final int direction = isLayoutRtl ? +1 : -1;
+ int menuStart = isLayoutRtl ? getPaddingLeft() : r - l - getPaddingRight();
+ // In LTR mode, we start from left padding and go to the right; in RTL mode, we start
+ // from the padding right and go to the left (in reverse way)
+ int x = isLayoutRtl ? r - l - getPaddingRight() : getPaddingLeft();
+ final int y = getPaddingTop();
+
HomeView homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
boolean needsTouchDelegate = false;
int homeSlop = mMaxHomeSlop;
int homeRight = 0;
if (homeLayout.getVisibility() != GONE) {
- final int leftOffset = homeLayout.getLeftOffset();
- x += positionChild(homeLayout, x + leftOffset, y, contentHeight) + leftOffset;
+ final int startOffset = homeLayout.getStartOffset();
+ x += positionChild(homeLayout,
+ next(x, startOffset, isLayoutRtl), y, contentHeight, isLayoutRtl);
+ x = next(x, startOffset, isLayoutRtl);
needsTouchDelegate = homeLayout == mHomeLayout;
homeRight = x;
}
@@ -1034,7 +1050,7 @@ public class ActionBarView extends AbsActionBarView {
final boolean showTitle = mTitleLayout != null && mTitleLayout.getVisibility() != GONE &&
(mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0;
if (showTitle) {
- x += positionChild(mTitleLayout, x, y, contentHeight);
+ x += positionChild(mTitleLayout, x, y, contentHeight, isLayoutRtl);
}
switch (mNavigationMode) {
@@ -1042,31 +1058,34 @@ public class ActionBarView extends AbsActionBarView {
break;
case ActionBar.NAVIGATION_MODE_LIST:
if (mListNavLayout != null) {
- if (showTitle) x += mItemPadding;
+ if (showTitle) {
+ x = next(x, mItemPadding, isLayoutRtl);
+ }
homeSlop = Math.min(homeSlop, Math.max(x - homeRight, 0));
- x += positionChild(mListNavLayout, x, y, contentHeight) + mItemPadding;
+ x += positionChild(mListNavLayout, x, y, contentHeight, isLayoutRtl);
+ x = next(x, mItemPadding, isLayoutRtl);
}
break;
case ActionBar.NAVIGATION_MODE_TABS:
if (mTabScrollView != null) {
- if (showTitle) x += mItemPadding;
+ if (showTitle) x = next(x, mItemPadding, isLayoutRtl);
homeSlop = Math.min(homeSlop, Math.max(x - homeRight, 0));
- x += positionChild(mTabScrollView, x, y, contentHeight) + mItemPadding;
+ x += positionChild(mTabScrollView, x, y, contentHeight, isLayoutRtl);
+ x = next(x, mItemPadding, isLayoutRtl);
}
break;
}
}
- int menuLeft = r - l - getPaddingRight();
if (mMenuView != null && mMenuView.getParent() == this) {
- positionChildInverse(mMenuView, menuLeft, y, contentHeight);
- menuLeft -= mMenuView.getMeasuredWidth();
+ positionChild(mMenuView, menuStart, y, contentHeight, !isLayoutRtl);
+ menuStart += direction * mMenuView.getMeasuredWidth();
}
if (mIndeterminateProgressView != null &&
mIndeterminateProgressView.getVisibility() != GONE) {
- positionChildInverse(mIndeterminateProgressView, menuLeft, y, contentHeight);
- menuLeft -= mIndeterminateProgressView.getMeasuredWidth();
+ positionChild(mIndeterminateProgressView, menuStart, y, contentHeight, !isLayoutRtl);
+ menuStart += direction * mIndeterminateProgressView.getMeasuredWidth();
}
View customView = null;
@@ -1077,51 +1096,64 @@ public class ActionBarView extends AbsActionBarView {
customView = mCustomNavView;
}
if (customView != null) {
+ final int resolvedLayoutDirection = getLayoutDirection();
ViewGroup.LayoutParams lp = customView.getLayoutParams();
+ lp.onResolveLayoutDirection(resolvedLayoutDirection);
final ActionBar.LayoutParams ablp = lp instanceof ActionBar.LayoutParams ?
(ActionBar.LayoutParams) lp : null;
-
final int gravity = ablp != null ? ablp.gravity : DEFAULT_CUSTOM_GRAVITY;
final int navWidth = customView.getMeasuredWidth();
int topMargin = 0;
int bottomMargin = 0;
if (ablp != null) {
- x += ablp.leftMargin;
- menuLeft -= ablp.rightMargin;
+ x = next(x, ablp.getMarginStart(), isLayoutRtl);
+ menuStart += direction * ablp.getMarginEnd();
topMargin = ablp.topMargin;
bottomMargin = ablp.bottomMargin;
}
- int hgravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ int hgravity = gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
// See if we actually have room to truly center; if not push against left or right.
if (hgravity == Gravity.CENTER_HORIZONTAL) {
final int centeredLeft = ((mRight - mLeft) - navWidth) / 2;
- if (centeredLeft < x) {
- hgravity = Gravity.LEFT;
- } else if (centeredLeft + navWidth > menuLeft) {
- hgravity = Gravity.RIGHT;
+ if (isLayoutRtl) {
+ final int centeredStart = centeredLeft + navWidth;
+ final int centeredEnd = centeredLeft;
+ if (centeredStart > x) {
+ hgravity = Gravity.RIGHT;
+ } else if (centeredEnd < menuStart) {
+ hgravity = Gravity.LEFT;
+ }
+ } else {
+ final int centeredStart = centeredLeft;
+ final int centeredEnd = centeredLeft + navWidth;
+ if (centeredStart < x) {
+ hgravity = Gravity.LEFT;
+ } else if (centeredEnd > menuStart) {
+ hgravity = Gravity.RIGHT;
+ }
}
- } else if (gravity == -1) {
- hgravity = Gravity.LEFT;
+ } else if (gravity == Gravity.NO_GRAVITY) {
+ hgravity = Gravity.START;
}
int xpos = 0;
- switch (hgravity) {
+ switch (Gravity.getAbsoluteGravity(hgravity, resolvedLayoutDirection)) {
case Gravity.CENTER_HORIZONTAL:
xpos = ((mRight - mLeft) - navWidth) / 2;
break;
case Gravity.LEFT:
- xpos = x;
+ xpos = isLayoutRtl ? menuStart : x;
break;
case Gravity.RIGHT:
- xpos = menuLeft - navWidth;
+ xpos = isLayoutRtl ? x - navWidth : menuStart - navWidth;
break;
}
int vgravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
- if (gravity == -1) {
+ if (gravity == Gravity.NO_GRAVITY) {
vgravity = Gravity.CENTER_VERTICAL;
}
@@ -1144,7 +1176,7 @@ public class ActionBarView extends AbsActionBarView {
customView.layout(xpos, ypos, xpos + customWidth,
ypos + customView.getMeasuredHeight());
homeSlop = Math.min(homeSlop, Math.max(xpos - homeRight, 0));
- x += customWidth;
+ x = next(x, customWidth, isLayoutRtl);
}
if (mProgressView != null) {
@@ -1247,12 +1279,19 @@ public class ActionBarView extends AbsActionBarView {
private ImageView mIconView;
private int mUpWidth;
+ private static final long DEFAULT_TRANSITION_DURATION = 150;
+
public HomeView(Context context) {
this(context, null);
}
public HomeView(Context context, AttributeSet attrs) {
super(context, attrs);
+ LayoutTransition t = getLayoutTransition();
+ if (t != null) {
+ // Set a lower duration than the default
+ t.setDuration(DEFAULT_TRANSITION_DURATION);
+ }
}
public void setUp(boolean isUp) {
@@ -1290,7 +1329,7 @@ public class ActionBarView extends AbsActionBarView {
mIconView = (ImageView) findViewById(com.android.internal.R.id.home);
}
- public int getLeftOffset() {
+ public int getStartOffset() {
return mUpView.getVisibility() == GONE ? mUpWidth : 0;
}
@@ -1340,25 +1379,51 @@ public class ActionBarView extends AbsActionBarView {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int vCenter = (b - t) / 2;
- int width = r - l;
+ final boolean isLayoutRtl = isLayoutRtl();
+ final int layoutDirection = getLayoutDirection();
+ final int width = getWidth();
int upOffset = 0;
if (mUpView.getVisibility() != GONE) {
final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams();
+ upLp.onResolveLayoutDirection(layoutDirection);
final int upHeight = mUpView.getMeasuredHeight();
final int upWidth = mUpView.getMeasuredWidth();
- final int upTop = vCenter - upHeight / 2;
- mUpView.layout(0, upTop, upWidth, upTop + upHeight);
upOffset = upLp.leftMargin + upWidth + upLp.rightMargin;
- width -= upOffset;
- l += upOffset;
+ final int upTop = vCenter - upHeight / 2;
+ final int upBottom = upTop + upHeight;
+ final int upRight;
+ final int upLeft;
+ if (isLayoutRtl) {
+ upRight = width;
+ upLeft = upRight - upWidth;
+ r -= upOffset;
+ } else {
+ upRight = upWidth;
+ upLeft = 0;
+ l += upOffset;
+ }
+ mUpView.layout(upLeft, upTop, upRight, upBottom);
}
+
final LayoutParams iconLp = (LayoutParams) mIconView.getLayoutParams();
+ iconLp.onResolveLayoutDirection(layoutDirection);
final int iconHeight = mIconView.getMeasuredHeight();
final int iconWidth = mIconView.getMeasuredWidth();
final int hCenter = (r - l) / 2;
- final int iconLeft = upOffset + Math.max(iconLp.leftMargin, hCenter - iconWidth / 2);
final int iconTop = Math.max(iconLp.topMargin, vCenter - iconHeight / 2);
- mIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
+ final int iconBottom = iconTop + iconHeight;
+ final int iconLeft;
+ final int iconRight;
+ int marginStart = iconLp.getMarginStart();
+ final int delta = Math.max(marginStart, hCenter - iconWidth / 2);
+ if (isLayoutRtl) {
+ iconRight = width - upOffset - delta;
+ iconLeft = iconRight - iconWidth;
+ } else {
+ iconLeft = upOffset + delta;
+ iconRight = iconLeft + iconWidth;
+ }
+ mIconView.layout(iconLeft, iconTop, iconRight, iconBottom);
}
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index f77e8f3..f987fc5 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -20,21 +20,19 @@ import com.android.internal.R;
import com.android.internal.telephony.ITelephony;
import com.google.android.collect.Lists;
+import android.app.ActivityManagerNative;
import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Binder;
-import android.os.FileObserver;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.UserId;
+import android.os.UserHandle;
import android.os.storage.IMountService;
import android.provider.Settings;
import android.security.KeyStore;
@@ -44,16 +42,10 @@ import android.util.Log;
import android.view.View;
import android.widget.Button;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
-import java.util.Arrays;
import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Utilities for the lock pattern and its settings.
@@ -133,7 +125,9 @@ public class LockPatternUtils {
private final ContentResolver mContentResolver;
private DevicePolicyManager mDevicePolicyManager;
private ILockSettings mLockSettingsService;
- private int mCurrentUserId = 0;
+
+ // The current user is set by KeyguardViewMediator and shared by all LockPatternUtils.
+ private static volatile int sCurrentUserId = UserHandle.USER_NULL;
public DevicePolicyManager getDevicePolicyManager() {
if (mDevicePolicyManager == null) {
@@ -164,7 +158,7 @@ public class LockPatternUtils {
}
public int getRequestedMinimumPasswordLength() {
- return getDevicePolicyManager().getPasswordMinimumLength(null);
+ return getDevicePolicyManager().getPasswordMinimumLength(null, getCurrentOrCallingUserId());
}
/**
@@ -172,81 +166,88 @@ public class LockPatternUtils {
* MODE_PATTERN which allows the user to choose anything.
*/
public int getRequestedPasswordQuality() {
- return getDevicePolicyManager().getPasswordQuality(null);
+ return getDevicePolicyManager().getPasswordQuality(null, getCurrentOrCallingUserId());
}
public int getRequestedPasswordHistoryLength() {
- return getDevicePolicyManager().getPasswordHistoryLength(null);
+ return getDevicePolicyManager().getPasswordHistoryLength(null, getCurrentOrCallingUserId());
}
public int getRequestedPasswordMinimumLetters() {
- return getDevicePolicyManager().getPasswordMinimumLetters(null);
+ return getDevicePolicyManager().getPasswordMinimumLetters(null,
+ getCurrentOrCallingUserId());
}
public int getRequestedPasswordMinimumUpperCase() {
- return getDevicePolicyManager().getPasswordMinimumUpperCase(null);
+ return getDevicePolicyManager().getPasswordMinimumUpperCase(null,
+ getCurrentOrCallingUserId());
}
public int getRequestedPasswordMinimumLowerCase() {
- return getDevicePolicyManager().getPasswordMinimumLowerCase(null);
+ return getDevicePolicyManager().getPasswordMinimumLowerCase(null,
+ getCurrentOrCallingUserId());
}
public int getRequestedPasswordMinimumNumeric() {
- return getDevicePolicyManager().getPasswordMinimumNumeric(null);
+ return getDevicePolicyManager().getPasswordMinimumNumeric(null,
+ getCurrentOrCallingUserId());
}
public int getRequestedPasswordMinimumSymbols() {
- return getDevicePolicyManager().getPasswordMinimumSymbols(null);
+ return getDevicePolicyManager().getPasswordMinimumSymbols(null,
+ getCurrentOrCallingUserId());
}
public int getRequestedPasswordMinimumNonLetter() {
- return getDevicePolicyManager().getPasswordMinimumNonLetter(null);
+ return getDevicePolicyManager().getPasswordMinimumNonLetter(null,
+ getCurrentOrCallingUserId());
}
+
/**
* Returns the actual password mode, as set by keyguard after updating the password.
*
* @return
*/
public void reportFailedPasswordAttempt() {
- getDevicePolicyManager().reportFailedPasswordAttempt();
+ getDevicePolicyManager().reportFailedPasswordAttempt(getCurrentOrCallingUserId());
}
public void reportSuccessfulPasswordAttempt() {
- getDevicePolicyManager().reportSuccessfulPasswordAttempt();
+ getDevicePolicyManager().reportSuccessfulPasswordAttempt(getCurrentOrCallingUserId());
}
public void setCurrentUser(int userId) {
- if (Process.myUid() == Process.SYSTEM_UID) {
- mCurrentUserId = userId;
- } else {
- throw new SecurityException("Only the system process can set the current user");
- }
+ sCurrentUserId = userId;
}
public int getCurrentUser() {
- if (Process.myUid() == Process.SYSTEM_UID) {
- return mCurrentUserId;
- } else {
- throw new SecurityException("Only the system process can get the current user");
+ if (sCurrentUserId != UserHandle.USER_NULL) {
+ // Someone is regularly updating using setCurrentUser() use that value.
+ return sCurrentUserId;
+ }
+ try {
+ return ActivityManagerNative.getDefault().getCurrentUser().id;
+ } catch (RemoteException re) {
+ return UserHandle.USER_OWNER;
}
}
public void removeUser(int userId) {
- if (Process.myUid() == Process.SYSTEM_UID) {
- try {
- getLockSettings().removeUser(userId);
- } catch (RemoteException re) {
- Log.e(TAG, "Couldn't remove lock settings for user " + userId);
- }
+ try {
+ getLockSettings().removeUser(userId);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Couldn't remove lock settings for user " + userId);
}
}
private int getCurrentOrCallingUserId() {
int callingUid = Binder.getCallingUid();
if (callingUid == android.os.Process.SYSTEM_UID) {
- return mCurrentUserId;
+ // TODO: This is a little inefficient. See if all users of this are able to
+ // handle USER_CURRENT and pass that instead.
+ return getCurrentUser();
} else {
- return UserId.getUserId(callingUid);
+ return UserHandle.getUserId(callingUid);
}
}
@@ -257,9 +258,13 @@ public class LockPatternUtils {
* @return Whether the pattern matches the stored one.
*/
public boolean checkPattern(List<LockPatternView.Cell> pattern) {
- int userId = getCurrentOrCallingUserId();
+ final int userId = getCurrentOrCallingUserId();
try {
- return getLockSettings().checkPattern(patternToHash(pattern), userId);
+ final boolean matched = getLockSettings().checkPattern(patternToHash(pattern), userId);
+ if (matched && (userId == UserHandle.USER_OWNER)) {
+ KeyStore.getInstance().password(patternToString(pattern));
+ }
+ return matched;
} catch (RemoteException re) {
return true;
}
@@ -272,9 +277,14 @@ public class LockPatternUtils {
* @return Whether the password matches the stored one.
*/
public boolean checkPassword(String password) {
- int userId = getCurrentOrCallingUserId();
+ final int userId = getCurrentOrCallingUserId();
try {
- return getLockSettings().checkPassword(passwordToHash(password), userId);
+ final boolean matched = getLockSettings().checkPassword(passwordToHash(password),
+ userId);
+ if (matched && (userId == UserHandle.USER_OWNER)) {
+ KeyStore.getInstance().password(password);
+ }
+ return matched;
} catch (RemoteException re) {
return true;
}
@@ -476,21 +486,21 @@ public class LockPatternUtils {
deleteGallery();
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
- pattern.size(), 0, 0, 0, 0, 0, 0);
+ pattern.size(), 0, 0, 0, 0, 0, 0, getCurrentOrCallingUserId());
} else {
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK);
setLong(PASSWORD_TYPE_ALTERNATE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
finishBiometricWeak();
dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK,
- 0, 0, 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0, 0, getCurrentOrCallingUserId());
}
} else {
if (keyStore.isEmpty()) {
keyStore.reset();
}
dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, getCurrentOrCallingUserId());
}
} catch (RemoteException re) {
Log.e(TAG, "Couldn't save lock pattern " + re);
@@ -527,7 +537,8 @@ public class LockPatternUtils {
/** Update the encryption password if it is enabled **/
private void updateEncryptionPassword(String password) {
DevicePolicyManager dpm = getDevicePolicyManager();
- if (dpm.getStorageEncryptionStatus() != DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) {
+ if (dpm.getStorageEncryptionStatus(getCurrentOrCallingUserId())
+ != DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) {
return;
}
@@ -553,7 +564,7 @@ public class LockPatternUtils {
* @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
*/
public void saveLockPassword(String password, int quality) {
- this.saveLockPassword(password, quality, false);
+ this.saveLockPassword(password, quality, false, getCurrentOrCallingUserId());
}
/**
@@ -565,23 +576,38 @@ public class LockPatternUtils {
* @param isFallback Specifies if this is a fallback to biometric weak
*/
public void saveLockPassword(String password, int quality, boolean isFallback) {
+ saveLockPassword(password, quality, isFallback, getCurrentOrCallingUserId());
+ }
+
+ /**
+ * Save a lock password. Does not ensure that the password is as good
+ * as the requested mode, but will adjust the mode to be as good as the
+ * pattern.
+ * @param password The password to save
+ * @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
+ * @param isFallback Specifies if this is a fallback to biometric weak
+ * @param userHandle The userId of the user to change the password for
+ */
+ public void saveLockPassword(String password, int quality, boolean isFallback, int userHandle) {
// Compute the hash
final byte[] hash = passwordToHash(password);
try {
- getLockSettings().setLockPassword(hash, getCurrentOrCallingUserId());
+ getLockSettings().setLockPassword(hash, userHandle);
DevicePolicyManager dpm = getDevicePolicyManager();
KeyStore keyStore = KeyStore.getInstance();
if (password != null) {
- // Update the encryption password.
- updateEncryptionPassword(password);
+ if (userHandle == UserHandle.USER_OWNER) {
+ // Update the encryption password.
+ updateEncryptionPassword(password);
- // Update the keystore password
- keyStore.password(password);
+ // Update the keystore password
+ keyStore.password(password);
+ }
int computedQuality = computePasswordQuality(password);
if (!isFallback) {
deleteGallery();
- setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality));
+ setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality), userHandle);
if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
int letters = 0;
int uppercase = 0;
@@ -607,25 +633,27 @@ public class LockPatternUtils {
}
dpm.setActivePasswordState(Math.max(quality, computedQuality),
password.length(), letters, uppercase, lowercase,
- numbers, symbols, nonletter);
+ numbers, symbols, nonletter, userHandle);
} else {
// The password is not anything.
dpm.setActivePasswordState(
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- 0, 0, 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0, 0, userHandle);
}
} else {
// Case where it's a fallback for biometric weak
- setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK);
- setLong(PASSWORD_TYPE_ALTERNATE_KEY, Math.max(quality, computedQuality));
+ setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK,
+ userHandle);
+ setLong(PASSWORD_TYPE_ALTERNATE_KEY, Math.max(quality, computedQuality),
+ userHandle);
finishBiometricWeak();
dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK,
- 0, 0, 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0, 0, userHandle);
}
// Add the password to the password history. We assume all
// password
// hashes have the same length for simplicity of implementation.
- String passwordHistory = getString(PASSWORD_HISTORY_KEY);
+ String passwordHistory = getString(PASSWORD_HISTORY_KEY, userHandle);
if (passwordHistory == null) {
passwordHistory = new String();
}
@@ -640,7 +668,7 @@ public class LockPatternUtils {
* passwordHistoryLength + passwordHistoryLength - 1, passwordHistory
.length()));
}
- setString(PASSWORD_HISTORY_KEY, passwordHistory);
+ setString(PASSWORD_HISTORY_KEY, passwordHistory, userHandle);
} else {
// Conditionally reset the keystore if empty. If
// non-empty, we are just switching key guard type
@@ -648,7 +676,8 @@ public class LockPatternUtils {
keyStore.reset();
}
dpm.setActivePasswordState(
- DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0);
+ DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0,
+ userHandle);
}
} catch (RemoteException re) {
// Cant do much
@@ -844,7 +873,7 @@ public class LockPatternUtils {
if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
return false;
}
- if (getDevicePolicyManager().getCameraDisabled(null)) {
+ if (getDevicePolicyManager().getCameraDisabled(null, getCurrentOrCallingUserId())) {
return false;
}
@@ -975,8 +1004,8 @@ public class LockPatternUtils {
* or null if there is no next alarm.
*/
public String getNextAlarm() {
- String nextAlarm = Settings.System.getString(mContentResolver,
- Settings.System.NEXT_ALARM_FORMATTED);
+ String nextAlarm = Settings.System.getStringForUser(mContentResolver,
+ Settings.System.NEXT_ALARM_FORMATTED, UserHandle.USER_CURRENT);
if (nextAlarm == null || TextUtils.isEmpty(nextAlarm)) {
return null;
}
@@ -1001,6 +1030,30 @@ public class LockPatternUtils {
}
}
+ public int[] getUserDefinedWidgets() {
+ int appWidgetId = -1;
+ String appWidgetIdString = Settings.Secure.getStringForUser(
+ mContentResolver, Settings.Secure.LOCK_SCREEN_USER_SELECTED_APPWIDGET_ID,
+ UserHandle.USER_CURRENT);
+ if (appWidgetIdString != null) {
+ appWidgetId = (int) Integer.decode(appWidgetIdString);
+ }
+
+ return new int[] { appWidgetId };
+ }
+
+ public int getStatusWidget() {
+ int appWidgetId = -1;
+ String appWidgetIdString = Settings.Secure.getStringForUser(
+ mContentResolver, Settings.Secure.LOCK_SCREEN_STATUS_APPWIDGET_ID,
+ UserHandle.USER_CURRENT);
+ if (appWidgetIdString != null) {
+ appWidgetId = (int) Integer.decode(appWidgetIdString);
+ }
+
+ return appWidgetId;
+ }
+
private long getLong(String secureSettingKey, long defaultValue) {
try {
return getLockSettings().getLong(secureSettingKey, defaultValue,
@@ -1011,6 +1064,10 @@ public class LockPatternUtils {
}
private void setLong(String secureSettingKey, long value) {
+ setLong(secureSettingKey, value, getCurrentOrCallingUserId());
+ }
+
+ private void setLong(String secureSettingKey, long value, int userHandle) {
try {
getLockSettings().setLong(secureSettingKey, value, getCurrentOrCallingUserId());
} catch (RemoteException re) {
@@ -1020,17 +1077,20 @@ public class LockPatternUtils {
}
private String getString(String secureSettingKey) {
+ return getString(secureSettingKey, getCurrentOrCallingUserId());
+ }
+
+ private String getString(String secureSettingKey, int userHandle) {
try {
- return getLockSettings().getString(secureSettingKey, null,
- getCurrentOrCallingUserId());
+ return getLockSettings().getString(secureSettingKey, null, userHandle);
} catch (RemoteException re) {
return null;
}
}
- private void setString(String secureSettingKey, String value) {
+ private void setString(String secureSettingKey, String value, int userHandle) {
try {
- getLockSettings().setString(secureSettingKey, value, getCurrentOrCallingUserId());
+ getLockSettings().setString(secureSettingKey, value, userHandle);
} catch (RemoteException re) {
// What can we do?
Log.e(TAG, "Couldn't write string " + secureSettingKey + re);
@@ -1062,8 +1122,13 @@ public class LockPatternUtils {
* {@link TelephonyManager#CALL_STATE_RINGING}
* {@link TelephonyManager#CALL_STATE_OFFHOOK}
* @param shown indicates whether the given screen wants the emergency button to show at all
+ * @param button
+ * @param phoneState
+ * @param shown shown if true; hidden if false
+ * @param upperCase if true, converts button label string to upper case
*/
- public void updateEmergencyCallButtonState(Button button, int phoneState, boolean shown) {
+ public void updateEmergencyCallButtonState(Button button, int phoneState, boolean shown,
+ boolean upperCase, boolean showIcon) {
if (isEmergencyCallCapable() && shown) {
button.setVisibility(View.VISIBLE);
} else {
@@ -1075,14 +1140,30 @@ public class LockPatternUtils {
if (phoneState == TelephonyManager.CALL_STATE_OFFHOOK) {
// show "return to call" text and show phone icon
textId = R.string.lockscreen_return_to_call;
- int phoneCallIcon = R.drawable.stat_sys_phone_call;
+ int phoneCallIcon = showIcon ? R.drawable.stat_sys_phone_call : 0;
button.setCompoundDrawablesWithIntrinsicBounds(phoneCallIcon, 0, 0, 0);
} else {
textId = R.string.lockscreen_emergency_call;
- int emergencyIcon = R.drawable.ic_emergency;
+ int emergencyIcon = showIcon ? R.drawable.ic_emergency : 0;
button.setCompoundDrawablesWithIntrinsicBounds(emergencyIcon, 0, 0, 0);
}
- button.setText(textId);
+ if (upperCase) {
+ CharSequence original = mContext.getResources().getText(textId);
+ String upper = original != null ? original.toString().toUpperCase() : null;
+ button.setText(upper);
+ } else {
+ button.setText(textId);
+ }
+ }
+
+ /**
+ * @deprecated
+ * @param button
+ * @param phoneState
+ * @param shown
+ */
+ public void updateEmergencyCallButtonState(Button button, int phoneState, boolean shown) {
+ updateEmergencyCallButtonState(button, phoneState, shown, false, true);
}
/**
diff --git a/core/java/com/android/internal/widget/LockSettingsService.java b/core/java/com/android/internal/widget/LockSettingsService.java
index 24c7161..4ecbd16 100644
--- a/core/java/com/android/internal/widget/LockSettingsService.java
+++ b/core/java/com/android/internal/widget/LockSettingsService.java
@@ -23,8 +23,10 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Binder;
+import android.os.Environment;
import android.os.RemoteException;
-import android.os.UserId;
+import android.os.SystemProperties;
+import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.text.TextUtils;
@@ -96,7 +98,7 @@ public class LockSettingsService extends ILockSettings.Stub {
private static final void checkWritePermission(int userId) {
final int callingUid = Binder.getCallingUid();
- if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
+ if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
throw new SecurityException("uid=" + callingUid
+ " not authorized to write lock settings");
}
@@ -104,7 +106,7 @@ public class LockSettingsService extends ILockSettings.Stub {
private static final void checkPasswordReadPermission(int userId) {
final int callingUid = Binder.getCallingUid();
- if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
+ if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
throw new SecurityException("uid=" + callingUid
+ " not authorized to read lock password");
}
@@ -112,8 +114,8 @@ public class LockSettingsService extends ILockSettings.Stub {
private static final void checkReadPermission(int userId) {
final int callingUid = Binder.getCallingUid();
- if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID
- && UserId.getUserId(callingUid) != userId) {
+ if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID
+ && UserHandle.getUserId(callingUid) != userId) {
throw new SecurityException("uid=" + callingUid
+ " not authorized to read settings of user " + userId);
}
@@ -172,7 +174,8 @@ public class LockSettingsService extends ILockSettings.Stub {
// Leave it in the same place for user 0
return dataSystemDirectory + LOCK_PATTERN_FILE;
} else {
- return dataSystemDirectory + "users/" + userId + "/" + LOCK_PATTERN_FILE;
+ return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
+ .getAbsolutePath();
}
}
@@ -184,7 +187,8 @@ public class LockSettingsService extends ILockSettings.Stub {
// Leave it in the same place for user 0
return dataSystemDirectory + LOCK_PASSWORD_FILE;
} else {
- return dataSystemDirectory + "users/" + userId + "/" + LOCK_PASSWORD_FILE;
+ return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
+ .getAbsolutePath();
}
}
@@ -303,12 +307,15 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private void writeToDb(String key, String value, int userId) {
+ writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId);
+ }
+
+ private void writeToDb(SQLiteDatabase db, String key, String value, int userId) {
ContentValues cv = new ContentValues();
cv.put(COLUMN_KEY, key);
cv.put(COLUMN_USERID, userId);
cv.put(COLUMN_VALUE, value);
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
db.beginTransaction();
try {
db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
@@ -359,6 +366,16 @@ public class LockSettingsService extends ILockSettings.Stub {
@Override
public void onCreate(SQLiteDatabase db) {
createTable(db);
+ initializeDefaults(db);
+ }
+
+ private void initializeDefaults(SQLiteDatabase db) {
+ // Get the lockscreen default from a system property, if available
+ boolean lockScreenDisable = SystemProperties.getBoolean("ro.lockscreen.disable.default",
+ false);
+ if (lockScreenDisable) {
+ writeToDb(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
+ }
}
@Override
diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
index 26518eb..f8332c4 100644
--- a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
+++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java
@@ -31,6 +31,7 @@ import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
import android.view.ViewRootImpl;
import com.android.internal.R;
@@ -55,23 +56,56 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener {
private long[] mVibratePattern;
private boolean mEnableHaptics = false;
+ private static final int NUMERIC = 0;
+ private static final int QWERTY = 1;
+ private static final int QWERTY_SHIFTED = 2;
+ private static final int SYMBOLS = 3;
+ private static final int SYMBOLS_SHIFTED = 4;
+
+ int mLayouts[] = new int[] {
+ R.xml.password_kbd_numeric,
+ R.xml.password_kbd_qwerty,
+ R.xml.password_kbd_qwerty_shifted,
+ R.xml.password_kbd_symbols,
+ R.xml.password_kbd_symbols_shift
+ };
+
+ private boolean mUsingScreenWidth;
+
public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView) {
- this(context, keyboardView, targetView, true);
+ this(context, keyboardView, targetView, true, null);
}
public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView,
boolean useFullScreenWidth) {
+ this(context, keyboardView, targetView, useFullScreenWidth, null);
+ }
+
+ public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView,
+ boolean useFullScreenWidth, int layouts[]) {
mContext = context;
mTargetView = targetView;
mKeyboardView = keyboardView;
- if (useFullScreenWidth
- || mKeyboardView.getLayoutParams().width == ViewGroup.LayoutParams.MATCH_PARENT) {
- createKeyboards();
+ mKeyboardView.setOnKeyboardActionListener(this);
+ mUsingScreenWidth = useFullScreenWidth;
+ if (layouts != null) {
+ if (layouts.length != mLayouts.length) {
+ throw new RuntimeException("Wrong number of layouts");
+ }
+ for (int i = 0; i < mLayouts.length; i++) {
+ mLayouts[i] = layouts[i];
+ }
+ }
+ createKeyboards();
+ }
+
+ public void createKeyboards() {
+ LayoutParams lp = mKeyboardView.getLayoutParams();
+ if (mUsingScreenWidth || lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
+ createKeyboardsWithDefaultWidth();
} else {
- createKeyboardsWithSpecificSize(mKeyboardView.getLayoutParams().width,
- mKeyboardView.getLayoutParams().height);
+ createKeyboardsWithSpecificSize(lp.width, lp.height);
}
- mKeyboardView.setOnKeyboardActionListener(this);
}
public void setEnableHaptics(boolean enabled) {
@@ -82,46 +116,40 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener {
return mKeyboardMode == KEYBOARD_MODE_ALPHA;
}
- private void createKeyboardsWithSpecificSize(int viewWidth, int viewHeight) {
- mNumericKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_numeric,
- viewWidth, viewHeight);
- mQwertyKeyboard = new PasswordEntryKeyboard(mContext,
- R.xml.password_kbd_qwerty, R.id.mode_normal, viewWidth, viewHeight);
+ private void createKeyboardsWithSpecificSize(int width, int height) {
+ mNumericKeyboard = new PasswordEntryKeyboard(mContext, mLayouts[NUMERIC], width, height);
+ mQwertyKeyboard = new PasswordEntryKeyboard(mContext, mLayouts[QWERTY], R.id.mode_normal,
+ width, height);
mQwertyKeyboard.enableShiftLock();
- mQwertyKeyboardShifted = new PasswordEntryKeyboard(mContext,
- R.xml.password_kbd_qwerty_shifted,
- R.id.mode_normal, viewWidth, viewHeight);
+ mQwertyKeyboardShifted = new PasswordEntryKeyboard(mContext, mLayouts[QWERTY_SHIFTED],
+ R.id.mode_normal, width, height);
mQwertyKeyboardShifted.enableShiftLock();
mQwertyKeyboardShifted.setShifted(true); // always shifted.
- mSymbolsKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_symbols,
- viewWidth, viewHeight);
+ mSymbolsKeyboard = new PasswordEntryKeyboard(mContext, mLayouts[SYMBOLS], width, height);
mSymbolsKeyboard.enableShiftLock();
- mSymbolsKeyboardShifted = new PasswordEntryKeyboard(mContext,
- R.xml.password_kbd_symbols_shift, viewWidth, viewHeight);
+ mSymbolsKeyboardShifted = new PasswordEntryKeyboard(mContext, mLayouts[SYMBOLS_SHIFTED],
+ width, height);
mSymbolsKeyboardShifted.enableShiftLock();
mSymbolsKeyboardShifted.setShifted(true); // always shifted
}
- private void createKeyboards() {
- mNumericKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_numeric);
- mQwertyKeyboard = new PasswordEntryKeyboard(mContext,
- R.xml.password_kbd_qwerty, R.id.mode_normal);
+ private void createKeyboardsWithDefaultWidth() {
+ mNumericKeyboard = new PasswordEntryKeyboard(mContext, mLayouts[NUMERIC]);
+ mQwertyKeyboard = new PasswordEntryKeyboard(mContext, mLayouts[QWERTY], R.id.mode_normal);
mQwertyKeyboard.enableShiftLock();
- mQwertyKeyboardShifted = new PasswordEntryKeyboard(mContext,
- R.xml.password_kbd_qwerty_shifted,
+ mQwertyKeyboardShifted = new PasswordEntryKeyboard(mContext, mLayouts[QWERTY_SHIFTED],
R.id.mode_normal);
mQwertyKeyboardShifted.enableShiftLock();
mQwertyKeyboardShifted.setShifted(true); // always shifted.
- mSymbolsKeyboard = new PasswordEntryKeyboard(mContext, R.xml.password_kbd_symbols);
+ mSymbolsKeyboard = new PasswordEntryKeyboard(mContext, mLayouts[SYMBOLS]);
mSymbolsKeyboard.enableShiftLock();
- mSymbolsKeyboardShifted = new PasswordEntryKeyboard(mContext,
- R.xml.password_kbd_symbols_shift);
+ mSymbolsKeyboardShifted = new PasswordEntryKeyboard(mContext, mLayouts[SYMBOLS_SHIFTED]);
mSymbolsKeyboardShifted.enableShiftLock();
mSymbolsKeyboardShifted.setShifted(true); // always shifted
}
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index 83ac896..b620568 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -23,7 +23,9 @@ import android.animation.TimeInterpolator;
import android.app.ActionBar;
import android.content.Context;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.view.Gravity;
import android.view.View;
@@ -38,6 +40,7 @@ import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
+import android.widget.Toast;
/**
* This widget implements the dynamic action bar tab behavior that can change
@@ -352,7 +355,7 @@ public class ScrollingTabContainerView extends HorizontalScrollView
tabView.getTab().select();
}
- private class TabView extends LinearLayout {
+ private class TabView extends LinearLayout implements OnLongClickListener {
private ActionBar.Tab mTab;
private TextView mTextView;
private ImageView mIconView;
@@ -363,7 +366,7 @@ public class ScrollingTabContainerView extends HorizontalScrollView
mTab = tab;
if (forList) {
- setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+ setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
}
update();
@@ -426,7 +429,8 @@ public class ScrollingTabContainerView extends HorizontalScrollView
mIconView.setImageDrawable(null);
}
- if (text != null) {
+ final boolean hasText = !TextUtils.isEmpty(text);
+ if (hasText) {
if (mTextView == null) {
TextView textView = new TextView(getContext(), null,
com.android.internal.R.attr.actionBarTabTextStyle);
@@ -448,9 +452,35 @@ public class ScrollingTabContainerView extends HorizontalScrollView
if (mIconView != null) {
mIconView.setContentDescription(tab.getContentDescription());
}
+
+ if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) {
+ setOnLongClickListener(this);
+ } else {
+ setOnLongClickListener(null);
+ setLongClickable(false);
+ }
}
}
+ public boolean onLongClick(View v) {
+ final int[] screenPos = new int[2];
+ getLocationOnScreen(screenPos);
+
+ final Context context = getContext();
+ final int width = getWidth();
+ final int height = getHeight();
+ final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
+
+ Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
+ Toast.LENGTH_SHORT);
+ // Show under the tab
+ cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
+ (screenPos[0] + width / 2) - screenWidth / 2, height);
+
+ cheatSheet.show();
+ return true;
+ }
+
public ActionBar.Tab getTab() {
return mTab;
}
diff --git a/core/java/com/android/internal/widget/TextProgressBar.java b/core/java/com/android/internal/widget/TextProgressBar.java
index e113dd8..e898aa4 100644
--- a/core/java/com/android/internal/widget/TextProgressBar.java
+++ b/core/java/com/android/internal/widget/TextProgressBar.java
@@ -155,7 +155,7 @@ public class TextProgressBar extends RelativeLayout implements OnChronometerTick
// Calculate any adjustment based on gravity
int adjustLeft = 0;
int textWidth = mChronometer.getWidth();
- if (mChronometerGravity == Gravity.RIGHT) {
+ if (mChronometerGravity == Gravity.END) {
adjustLeft = -textWidth;
} else if (mChronometerGravity == Gravity.CENTER_HORIZONTAL) {
adjustLeft = -(textWidth / 2);
diff --git a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
index 4e60b75..549d74c 100644
--- a/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/GlowPadView.java
@@ -39,7 +39,6 @@ import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.R;
@@ -197,6 +196,7 @@ public class GlowPadView extends View {
private Tweener mBackgroundAnimator;
private PointCloud mPointCloud;
private float mInnerRadius;
+ private int mPointerId;
public GlowPadView(Context context) {
this(context, null);
@@ -214,8 +214,8 @@ public class GlowPadView extends View {
mVibrationDuration);
mFeedbackCount = a.getInt(R.styleable.GlowPadView_feedbackCount,
mFeedbackCount);
- mHandleDrawable = new TargetDrawable(res,
- a.peekValue(R.styleable.GlowPadView_handleDrawable).resourceId);
+ TypedValue handle = a.peekValue(R.styleable.GlowPadView_handleDrawable);
+ mHandleDrawable = new TargetDrawable(res, handle != null ? handle.resourceId : 0);
mHandleDrawable.setState(TargetDrawable.STATE_INACTIVE);
mOuterRing = new TargetDrawable(res,
getResourceId(a, R.styleable.GlowPadView_outerRingDrawable));
@@ -717,7 +717,7 @@ public class GlowPadView extends View {
startBackgroundAnimation(0, 0.0f);
stopAndHideWaveAnimation();
hideTargets(animate, false);
- hideGlow(0, 0, 1.0f, null);
+ hideGlow(0, 0, 0.0f, null);
Tweener.reset();
}
@@ -737,9 +737,10 @@ public class GlowPadView extends View {
@Override
public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getAction();
+ final int action = event.getActionMasked();
boolean handled = false;
switch (action) {
+ case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_DOWN:
if (DEBUG) Log.v(TAG, "*** DOWN ***");
handleDown(event);
@@ -753,6 +754,7 @@ public class GlowPadView extends View {
handled = true;
break;
+ case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_UP:
if (DEBUG) Log.v(TAG, "*** UP ***");
handleMove(event);
@@ -766,6 +768,7 @@ public class GlowPadView extends View {
handleCancel(event);
handled = true;
break;
+
}
invalidate();
return handled ? true : super.onTouchEvent(event);
@@ -777,19 +780,24 @@ public class GlowPadView extends View {
}
private void handleDown(MotionEvent event) {
- float eventX = event.getX();
- float eventY = event.getY();
+ int actionIndex = event.getActionIndex();
+ float eventX = event.getX(actionIndex);
+ float eventY = event.getY(actionIndex);
switchToState(STATE_START, eventX, eventY);
if (!trySwitchToFirstTouchState(eventX, eventY)) {
mDragging = false;
} else {
+ mPointerId = event.getPointerId(actionIndex);
updateGlowPosition(eventX, eventY);
}
}
private void handleUp(MotionEvent event) {
if (DEBUG && mDragging) Log.v(TAG, "** Handle RELEASE");
- switchToState(STATE_FINISH, event.getX(), event.getY());
+ int actionIndex = event.getActionIndex();
+ if (event.getPointerId(actionIndex) == mPointerId) {
+ switchToState(STATE_FINISH, event.getX(actionIndex), event.getY(actionIndex));
+ }
}
private void handleCancel(MotionEvent event) {
@@ -802,7 +810,9 @@ public class GlowPadView extends View {
// mActiveTarget = -1; // Drop the active target if canceled.
- switchToState(STATE_FINISH, event.getX(), event.getY());
+ int actionIndex = event.findPointerIndex(mPointerId);
+ actionIndex = actionIndex == -1 ? 0 : actionIndex;
+ switchToState(STATE_FINISH, event.getX(actionIndex), event.getY(actionIndex));
}
private void handleMove(MotionEvent event) {
@@ -812,9 +822,17 @@ public class GlowPadView extends View {
int ntargets = targets.size();
float x = 0.0f;
float y = 0.0f;
+ int actionIndex = event.findPointerIndex(mPointerId);
+
+ if (actionIndex == -1) {
+ return; // no data for this pointer
+ }
+
for (int k = 0; k < historySize + 1; k++) {
- float eventX = k < historySize ? event.getHistoricalX(k) : event.getX();
- float eventY = k < historySize ? event.getHistoricalY(k) : event.getY();
+ float eventX = k < historySize ? event.getHistoricalX(actionIndex, k)
+ : event.getX(actionIndex);
+ float eventY = k < historySize ? event.getHistoricalY(actionIndex, k)
+ : event.getY(actionIndex);
// tx and ty are relative to wave center
float tx = eventX - mWaveCenterX;
float ty = eventY - mWaveCenterY;
@@ -957,7 +975,7 @@ public class GlowPadView extends View {
}
private void computeInsets(int dx, int dy) {
- final int layoutDirection = getResolvedLayoutDirection();
+ final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
diff --git a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
index 10804c0..7990b4c 100644
--- a/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
+++ b/core/java/com/android/internal/widget/multiwaveview/MultiWaveView.java
@@ -19,7 +19,6 @@ package com.android.internal.widget.multiwaveview;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -970,7 +969,7 @@ public class MultiWaveView extends View {
}
private void computeInsets(int dx, int dy) {
- final int layoutDirection = getResolvedLayoutDirection();
+ final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {