summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java107
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java314
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl5
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl132
-rw-r--r--core/java/android/accessibilityservice/UiTestAutomationBridge.java496
-rw-r--r--core/java/android/accounts/AbstractAccountAuthenticator.java80
-rw-r--r--core/java/android/accounts/AccountAuthenticatorCache.java91
-rw-r--r--core/java/android/accounts/AccountAuthenticatorResponse.java2
-rw-r--r--core/java/android/accounts/AccountManager.java116
-rw-r--r--core/java/android/accounts/AccountManagerService.java2571
-rw-r--r--core/java/android/accounts/CantAddAccountActivity.java40
-rw-r--r--core/java/android/accounts/ChooseTypeAndAccountActivity.java52
-rw-r--r--core/java/android/accounts/IAccountAuthenticator.aidl13
-rw-r--r--core/java/android/accounts/IAccountAuthenticatorCache.java66
-rw-r--r--core/java/android/accounts/IAccountManager.aidl11
-rw-r--r--core/java/android/animation/Animator.java31
-rw-r--r--core/java/android/animation/AnimatorInflater.java2
-rw-r--r--core/java/android/animation/AnimatorSet.java31
-rw-r--r--core/java/android/animation/KeyframeSet.java17
-rw-r--r--core/java/android/animation/ObjectAnimator.java73
-rw-r--r--core/java/android/animation/RectEvaluator.java45
-rw-r--r--core/java/android/animation/ValueAnimator.java43
-rw-r--r--core/java/android/app/ActionBar.java80
-rw-r--r--core/java/android/app/Activity.java46
-rw-r--r--core/java/android/app/ActivityManager.java34
-rw-r--r--core/java/android/app/ActivityManagerNative.java181
-rw-r--r--core/java/android/app/ActivityThread.java73
-rw-r--r--core/java/android/app/AppOpsManager.aidl20
-rw-r--r--core/java/android/app/AppOpsManager.java541
-rw-r--r--core/java/android/app/Application.java51
-rw-r--r--core/java/android/app/ApplicationLoaders.java10
-rw-r--r--core/java/android/app/ApplicationPackageManager.java61
-rw-r--r--core/java/android/app/ApplicationThreadNative.java40
-rw-r--r--core/java/android/app/ContextImpl.java144
-rw-r--r--core/java/android/app/DownloadManager.java61
-rw-r--r--core/java/android/app/IActivityController.aidl7
-rw-r--r--core/java/android/app/IActivityManager.java37
-rw-r--r--core/java/android/app/IApplicationThread.java6
-rw-r--r--core/java/android/app/INotificationManager.aidl21
-rw-r--r--core/java/android/app/IUiAutomationConnection.aidl40
-rw-r--r--core/java/android/app/Instrumentation.java72
-rw-r--r--core/java/android/app/LoadedApk.java7
-rw-r--r--core/java/android/app/MediaRouteButton.java6
-rw-r--r--core/java/android/app/NativeActivity.java82
-rw-r--r--core/java/android/app/Notification.java319
-rw-r--r--core/java/android/app/NotificationManager.java15
-rw-r--r--core/java/android/app/PendingIntent.java75
-rw-r--r--core/java/android/app/SearchManager.java13
-rw-r--r--core/java/android/app/Service.java2
-rw-r--r--core/java/android/app/UiAutomation.java731
-rw-r--r--core/java/android/app/UiAutomationConnection.java245
-rw-r--r--core/java/android/app/WallpaperManager.java6
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java68
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl4
-rw-r--r--core/java/android/app/backup/BackupAgent.java85
-rw-r--r--core/java/android/app/backup/FullBackup.java3
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl7
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java124
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java75
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java355
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java73
-rw-r--r--core/java/android/bluetooth/BluetoothGatt.java1169
-rw-r--r--core/java/android/bluetooth/BluetoothGattCallback.java141
-rw-r--r--core/java/android/bluetooth/BluetoothGattCharacteristic.java682
-rw-r--r--core/java/android/bluetooth/BluetoothGattDescriptor.java206
-rw-r--r--core/java/android/bluetooth/BluetoothGattServer.java694
-rw-r--r--core/java/android/bluetooth/BluetoothGattServerCallback.java136
-rw-r--r--core/java/android/bluetooth/BluetoothGattService.java266
-rw-r--r--core/java/android/bluetooth/BluetoothManager.java219
-rw-r--r--core/java/android/bluetooth/BluetoothOutputStream.java11
-rw-r--r--core/java/android/bluetooth/BluetoothProfile.java10
-rw-r--r--core/java/android/bluetooth/BluetoothSocket.java12
-rw-r--r--core/java/android/bluetooth/BluetoothTetheringDataTracker.java219
-rw-r--r--core/java/android/bluetooth/IBluetooth.aidl1
-rw-r--r--core/java/android/bluetooth/IBluetoothGatt.aidl90
-rw-r--r--core/java/android/bluetooth/IBluetoothGattCallback.aidl65
-rw-r--r--core/java/android/bluetooth/IBluetoothGattServerCallback.aidl61
-rw-r--r--core/java/android/bluetooth/IBluetoothManager.aidl2
-rw-r--r--core/java/android/bluetooth/package.html13
-rw-r--r--core/java/android/content/AsyncTaskLoader.java4
-rw-r--r--core/java/android/content/BroadcastReceiver.java2
-rw-r--r--core/java/android/content/ClipData.java19
-rw-r--r--core/java/android/content/ClipboardManager.java16
-rw-r--r--core/java/android/content/ContentProvider.java272
-rw-r--r--core/java/android/content/ContentProviderClient.java24
-rw-r--r--core/java/android/content/ContentProviderNative.java89
-rw-r--r--core/java/android/content/ContentResolver.java84
-rw-r--r--core/java/android/content/ContentService.java838
-rw-r--r--core/java/android/content/ContentValues.aidl (renamed from core/java/android/util/Pool.java)12
-rw-r--r--core/java/android/content/Context.java63
-rw-r--r--core/java/android/content/ContextWrapper.java34
-rw-r--r--core/java/android/content/CursorLoader.java19
-rw-r--r--core/java/android/content/IClipboard.aidl11
-rw-r--r--core/java/android/content/IContentProvider.java27
-rw-r--r--core/java/android/content/Intent.java211
-rw-r--r--core/java/android/content/IntentFilter.java3
-rw-r--r--core/java/android/content/Loader.java42
-rw-r--r--core/java/android/content/PeriodicSync.java21
-rw-r--r--core/java/android/content/RestrictionEntry.aidl20
-rw-r--r--core/java/android/content/RestrictionEntry.java416
-rw-r--r--core/java/android/content/SharedPreferences.java8
-rw-r--r--core/java/android/content/SyncAdaptersCache.java4
-rw-r--r--core/java/android/content/SyncInfo.java2
-rw-r--r--core/java/android/content/SyncManager.java2632
-rw-r--r--core/java/android/content/SyncOperation.java171
-rw-r--r--core/java/android/content/SyncQueue.java220
-rw-r--r--core/java/android/content/SyncStatusInfo.java17
-rw-r--r--core/java/android/content/SyncStorageEngine.java2303
-rw-r--r--core/java/android/content/UriMatcher.java20
-rw-r--r--core/java/android/content/pm/ActivityInfo.java32
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl30
-rw-r--r--core/java/android/content/pm/ManifestDigest.java53
-rw-r--r--core/java/android/content/pm/PackageInfo.java17
-rw-r--r--core/java/android/content/pm/PackageManager.java174
-rw-r--r--core/java/android/content/pm/PackageParser.java224
-rw-r--r--core/java/android/content/pm/PackageUserState.java3
-rw-r--r--core/java/android/content/pm/ParceledListSlice.java215
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java3
-rw-r--r--core/java/android/content/pm/Signature.java11
-rw-r--r--core/java/android/content/pm/UserInfo.java4
-rw-r--r--core/java/android/content/res/AssetManager.java8
-rw-r--r--core/java/android/content/res/Configuration.java8
-rw-r--r--core/java/android/content/res/Resources.java330
-rw-r--r--core/java/android/content/res/StringBlock.java87
-rw-r--r--core/java/android/content/res/TypedArray.java2
-rw-r--r--core/java/android/database/CursorToBulkCursorAdaptor.java5
-rw-r--r--core/java/android/database/CursorWindow.aidl (renamed from core/java/android/util/PoolableManager.java)14
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java14
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java29
-rw-r--r--core/java/android/ddm/DdmHandleHello.java26
-rw-r--r--core/java/android/ddm/DdmHandleViewDebug.java416
-rw-r--r--core/java/android/ddm/DdmRegister.java1
-rw-r--r--core/java/android/hardware/Camera.java9
-rw-r--r--core/java/android/hardware/Sensor.java168
-rw-r--r--core/java/android/hardware/SensorEvent.java197
-rw-r--r--core/java/android/hardware/SensorManager.java139
-rw-r--r--core/java/android/hardware/SerialPort.java8
-rw-r--r--core/java/android/hardware/SystemSensorManager.java622
-rw-r--r--core/java/android/hardware/TriggerEvent.java62
-rw-r--r--core/java/android/hardware/TriggerEventListener.java77
-rw-r--r--core/java/android/hardware/input/InputManager.java22
-rw-r--r--core/java/android/hardware/location/GeofenceHardware.java501
-rw-r--r--core/java/android/hardware/location/GeofenceHardwareCallback.java87
-rw-r--r--core/java/android/hardware/location/GeofenceHardwareImpl.java773
-rw-r--r--core/java/android/hardware/location/GeofenceHardwareMonitorCallback.java37
-rw-r--r--core/java/android/hardware/location/GeofenceHardwareRequest.java158
-rw-r--r--core/java/android/hardware/location/GeofenceHardwareService.java139
-rw-r--r--core/java/android/hardware/location/IGeofenceHardware.aidl38
-rw-r--r--core/java/android/hardware/location/IGeofenceHardwareCallback.aidl29
-rw-r--r--core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl (renamed from core/java/com/android/internal/view/IInputConnectionCallback.aidl)19
-rw-r--r--core/java/android/hardware/usb/IUsbManager.aidl3
-rw-r--r--core/java/android/hardware/usb/UsbDeviceConnection.java79
-rw-r--r--core/java/android/inputmethodservice/AbstractInputMethodService.java5
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java170
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java58
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java11
-rw-r--r--core/java/android/net/BaseNetworkStateTracker.java21
-rw-r--r--core/java/android/net/CaptivePortalTracker.java184
-rw-r--r--core/java/android/net/ConnectivityManager.java604
-rw-r--r--core/java/android/net/DhcpInfo.java3
-rw-r--r--core/java/android/net/DhcpInfoInternal.java166
-rw-r--r--core/java/android/net/DhcpResults.aidl19
-rw-r--r--core/java/android/net/DhcpResults.java247
-rw-r--r--core/java/android/net/DhcpStateMachine.java21
-rw-r--r--core/java/android/net/DummyDataStateTracker.java22
-rw-r--r--core/java/android/net/EthernetDataTracker.java28
-rw-r--r--core/java/android/net/IConnectivityManager.aidl18
-rw-r--r--core/java/android/net/IThrottleManager.aidl40
-rw-r--r--core/java/android/net/LinkCapabilities.java2
-rw-r--r--core/java/android/net/LinkProperties.java212
-rw-r--r--core/java/android/net/LocalSocketImpl.java20
-rw-r--r--core/java/android/net/MobileDataStateTracker.java165
-rw-r--r--core/java/android/net/NetworkInfo.java34
-rw-r--r--core/java/android/net/NetworkStateTracker.java45
-rw-r--r--core/java/android/net/NetworkStats.java12
-rw-r--r--core/java/android/net/NetworkStatsHistory.java2
-rw-r--r--core/java/android/net/NetworkTemplate.java4
-rw-r--r--core/java/android/net/NetworkUtils.java13
-rw-r--r--core/java/android/net/RouteInfo.java96
-rw-r--r--core/java/android/net/ThrottleManager.java214
-rw-r--r--core/java/android/net/TrafficStats.java389
-rw-r--r--core/java/android/net/Uri.java13
-rw-r--r--core/java/android/net/http/AndroidHttpClient.java36
-rw-r--r--core/java/android/net/nsd/NsdManager.java3
-rw-r--r--core/java/android/nfc/BeamShareData.aidl (renamed from core/java/android/util/Poolable.java)14
-rw-r--r--core/java/android/nfc/BeamShareData.java62
-rw-r--r--core/java/android/nfc/INdefPushCallback.aidl6
-rw-r--r--core/java/android/nfc/NfcActivityManager.java67
-rw-r--r--core/java/android/nfc/NfcAdapter.java48
-rw-r--r--core/java/android/nfc/tech/NfcBarcode.java22
-rw-r--r--core/java/android/nfc/tech/TagTechnology.java1
-rw-r--r--core/java/android/os/BatteryStats.java42
-rw-r--r--core/java/android/os/Binder.java43
-rw-r--r--core/java/android/os/Build.java5
-rw-r--r--core/java/android/os/Bundle.java89
-rw-r--r--core/java/android/os/Debug.java1
-rw-r--r--core/java/android/os/Environment.java91
-rw-r--r--core/java/android/os/FileUtils.java45
-rw-r--r--core/java/android/os/Handler.java15
-rw-r--r--core/java/android/os/HandlerThread.java53
-rw-r--r--core/java/android/os/INetworkManagementService.aidl54
-rw-r--r--core/java/android/os/IUserManager.aidl9
-rw-r--r--core/java/android/os/IVibratorService.aidl4
-rw-r--r--core/java/android/os/LatencyTimer.java94
-rw-r--r--core/java/android/os/Looper.java45
-rw-r--r--core/java/android/os/MessageQueue.java99
-rw-r--r--core/java/android/os/NullVibrator.java16
-rw-r--r--core/java/android/os/Parcel.java43
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java7
-rw-r--r--core/java/android/os/Process.java15
-rw-r--r--core/java/android/os/SchedulingPolicyService.java65
-rw-r--r--core/java/android/os/StatFs.java72
-rw-r--r--core/java/android/os/StrictMode.java35
-rw-r--r--core/java/android/os/SystemClock.java2
-rw-r--r--core/java/android/os/SystemVibrator.java32
-rw-r--r--core/java/android/os/Trace.java165
-rw-r--r--core/java/android/os/UserHandle.java46
-rw-r--r--core/java/android/os/UserManager.java268
-rw-r--r--core/java/android/os/Vibrator.java14
-rw-r--r--core/java/android/os/WorkSource.java410
-rw-r--r--core/java/android/os/storage/StorageManager.java53
-rw-r--r--core/java/android/preference/PreferenceActivity.java11
-rw-r--r--core/java/android/provider/CalendarContract.java17
-rw-r--r--core/java/android/provider/ContactsContract.java120
-rw-r--r--core/java/android/provider/Downloads.java3
-rw-r--r--core/java/android/provider/Settings.java237
-rw-r--r--core/java/android/security/IKeystoreService.java57
-rw-r--r--core/java/android/server/package.html5
-rw-r--r--core/java/android/server/search/SearchManagerService.java279
-rw-r--r--core/java/android/server/search/Searchables.java464
-rw-r--r--core/java/android/server/search/package.html5
-rw-r--r--core/java/android/service/notification/INotificationListener.aidl26
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java174
-rw-r--r--core/java/android/service/notification/StatusBarNotification.aidl (renamed from core/java/com/android/internal/statusbar/StatusBarNotification.aidl)2
-rw-r--r--core/java/android/service/notification/StatusBarNotification.java244
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java28
-rw-r--r--core/java/android/speech/tts/FileSynthesisCallback.java94
-rw-r--r--core/java/android/speech/tts/ITextToSpeechService.aidl29
-rw-r--r--core/java/android/speech/tts/SynthesisCallback.java5
-rw-r--r--core/java/android/speech/tts/TextToSpeech.java436
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java178
-rw-r--r--core/java/android/test/AndroidTestCase.java10
-rw-r--r--core/java/android/text/BidiFormatter.java890
-rw-r--r--core/java/android/text/DynamicLayout.java27
-rw-r--r--core/java/android/text/GraphicsOperations.java7
-rw-r--r--core/java/android/text/Html.java58
-rw-r--r--core/java/android/text/Layout.java57
-rw-r--r--core/java/android/text/SpannableStringBuilder.java29
-rw-r--r--core/java/android/text/StaticLayout.java3
-rw-r--r--core/java/android/text/TextDirectionHeuristic.java28
-rw-r--r--core/java/android/text/TextDirectionHeuristics.java156
-rw-r--r--core/java/android/text/TextUtils.java22
-rw-r--r--core/java/android/text/format/DateFormat.java47
-rw-r--r--core/java/android/text/method/DigitsKeyListener.java39
-rw-r--r--core/java/android/text/method/QwertyKeyListener.java1
-rw-r--r--core/java/android/text/method/Touch.java4
-rw-r--r--core/java/android/text/style/EasyEditSpan.java85
-rw-r--r--core/java/android/text/style/SuggestionSpan.java49
-rw-r--r--core/java/android/text/util/Linkify.java23
-rw-r--r--core/java/android/util/DisplayMetrics.java9
-rw-r--r--core/java/android/util/FinitePool.java94
-rw-r--r--core/java/android/util/LongSparseLongArray.java228
-rw-r--r--core/java/android/util/Pools.java148
-rw-r--r--core/java/android/util/SparseLongArray.java2
-rw-r--r--core/java/android/util/SynchronizedPool.java48
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java238
-rw-r--r--core/java/android/view/Choreographer.java2
-rw-r--r--core/java/android/view/Display.java14
-rw-r--r--core/java/android/view/DisplayEventReceiver.java4
-rw-r--r--core/java/android/view/DisplayInfo.java101
-rw-r--r--core/java/android/view/DisplayList.java524
-rw-r--r--core/java/android/view/FocusFinder.java32
-rw-r--r--core/java/android/view/GLES20Canvas.java98
-rw-r--r--core/java/android/view/GLES20DisplayList.java204
-rw-r--r--core/java/android/view/GLES20Layer.java4
-rw-r--r--core/java/android/view/GLES20RecordingCanvas.java49
-rw-r--r--core/java/android/view/GLES20RenderLayer.java9
-rw-r--r--core/java/android/view/GestureDetector.java17
-rw-r--r--core/java/android/view/HardwareCanvas.java77
-rw-r--r--core/java/android/view/HardwareLayer.java11
-rw-r--r--core/java/android/view/HardwareRenderer.java879
-rw-r--r--core/java/android/view/IMagnificationCallbacks.aidl (renamed from core/java/android/view/IDisplayContentChangeListener.aidl)16
-rw-r--r--core/java/android/view/IWindow.aidl2
-rw-r--r--core/java/android/view/IWindowFocusObserver.aidl23
-rw-r--r--core/java/android/view/IWindowId.aidl26
-rw-r--r--core/java/android/view/IWindowManager.aidl72
-rw-r--r--core/java/android/view/IWindowSession.aidl8
-rw-r--r--core/java/android/view/InputChannel.java22
-rw-r--r--core/java/android/view/InputDevice.java58
-rw-r--r--core/java/android/view/InputEvent.java13
-rw-r--r--core/java/android/view/InputEventReceiver.java7
-rw-r--r--core/java/android/view/InputEventSender.java143
-rw-r--r--core/java/android/view/InputQueue.java130
-rw-r--r--core/java/android/view/KeyCharacterMap.java16
-rw-r--r--core/java/android/view/KeyEvent.java30
-rw-r--r--core/java/android/view/LayoutInflater.java11
-rw-r--r--core/java/android/view/MagnificationSpec.aidl (renamed from core/java/android/view/WindowInfo.aidl)2
-rw-r--r--core/java/android/view/MagnificationSpec.java123
-rw-r--r--core/java/android/view/MotionEvent.java8
-rw-r--r--core/java/android/view/SimulatedDpad.java298
-rw-r--r--core/java/android/view/Surface.java739
-rw-r--r--core/java/android/view/SurfaceControl.java628
-rw-r--r--core/java/android/view/SurfaceView.java40
-rw-r--r--core/java/android/view/TextureView.java10
-rw-r--r--core/java/android/view/VelocityTracker.java58
-rw-r--r--core/java/android/view/View.java852
-rw-r--r--core/java/android/view/ViewDebug.java175
-rw-r--r--core/java/android/view/ViewGroup.java684
-rw-r--r--core/java/android/view/ViewGroupOverlay.java76
-rw-r--r--core/java/android/view/ViewOverlay.java311
-rw-r--r--core/java/android/view/ViewParent.java109
-rw-r--r--core/java/android/view/ViewPropertyAnimator.java11
-rw-r--r--core/java/android/view/ViewRootImpl.java2851
-rw-r--r--core/java/android/view/ViewTreeObserver.java153
-rw-r--r--core/java/android/view/VolumePanel.java100
-rw-r--r--core/java/android/view/WindowId.java224
-rw-r--r--core/java/android/view/WindowInfo.java175
-rw-r--r--core/java/android/view/WindowManager.java163
-rw-r--r--core/java/android/view/WindowManagerGlobal.java29
-rw-r--r--core/java/android/view/WindowManagerPolicy.java157
-rw-r--r--core/java/android/view/WindowOrientationListener.java715
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java249
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java93
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java398
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfoCache.java1
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl15
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl2
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java3
-rw-r--r--core/java/android/view/inputmethod/CompletionInfo.java59
-rw-r--r--core/java/android/view/inputmethod/ExtractedText.java27
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java713
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.java56
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java436
-rw-r--r--core/java/android/webkit/AccessibilityInjector.java22
-rw-r--r--core/java/android/webkit/AccessibilityInjectorFallback.java149
-rw-r--r--core/java/android/webkit/BrowserFrame.java16
-rw-r--r--core/java/android/webkit/CacheManager.java126
-rw-r--r--core/java/android/webkit/CallbackProxy.java243
-rw-r--r--core/java/android/webkit/EventLogTags.logtags1
-rw-r--r--core/java/android/webkit/FindActionModeCallback.java36
-rw-r--r--core/java/android/webkit/GeolocationPermissions.java3
-rw-r--r--core/java/android/webkit/HTML5Audio.java9
-rw-r--r--core/java/android/webkit/HTML5VideoFullScreen.java8
-rw-r--r--core/java/android/webkit/HttpAuthHandler.java2
-rw-r--r--core/java/android/webkit/JsDialogHelper.java185
-rw-r--r--core/java/android/webkit/SslErrorHandler.java8
-rw-r--r--core/java/android/webkit/ViewStateSerializer.java3
-rw-r--r--core/java/android/webkit/WebChromeClient.java7
-rw-r--r--core/java/android/webkit/WebIconDatabase.java8
-rw-r--r--core/java/android/webkit/WebSettings.java99
-rw-r--r--core/java/android/webkit/WebSettingsClassic.java8
-rw-r--r--core/java/android/webkit/WebStorage.java2
-rw-r--r--core/java/android/webkit/WebView.java126
-rw-r--r--core/java/android/webkit/WebViewClassic.java78
-rw-r--r--core/java/android/webkit/WebViewClient.java9
-rw-r--r--core/java/android/webkit/WebViewCore.java19
-rw-r--r--core/java/android/webkit/WebViewDatabase.java24
-rw-r--r--core/java/android/webkit/WebViewFactory.java32
-rw-r--r--core/java/android/webkit/WebViewProvider.java10
-rw-r--r--core/java/android/widget/AbsListView.java119
-rw-r--r--core/java/android/widget/AbsSeekBar.java4
-rw-r--r--core/java/android/widget/AbsSpinner.java4
-rw-r--r--core/java/android/widget/AppSecurityPermissions.java176
-rw-r--r--core/java/android/widget/ArrayAdapter.java18
-rw-r--r--core/java/android/widget/CalendarView.java19
-rw-r--r--core/java/android/widget/DatePicker.java2
-rw-r--r--core/java/android/widget/Editor.java162
-rw-r--r--core/java/android/widget/ExpandableListView.java211
-rw-r--r--core/java/android/widget/FastScroller.java40
-rw-r--r--core/java/android/widget/Gallery.java2
-rw-r--r--core/java/android/widget/GridLayout.java120
-rw-r--r--core/java/android/widget/HeaderViewListAdapter.java3
-rw-r--r--core/java/android/widget/ImageView.java63
-rw-r--r--core/java/android/widget/LinearLayout.java28
-rw-r--r--core/java/android/widget/ListView.java99
-rw-r--r--core/java/android/widget/MediaController.java19
-rw-r--r--core/java/android/widget/NumberPicker.java17
-rw-r--r--core/java/android/widget/ProgressBar.java65
-rw-r--r--core/java/android/widget/QuickContactBadge.java63
-rw-r--r--core/java/android/widget/RelativeLayout.java366
-rw-r--r--core/java/android/widget/RemoteViews.java147
-rw-r--r--core/java/android/widget/RemoteViewsListAdapter.java121
-rw-r--r--core/java/android/widget/SearchView.java1
-rw-r--r--core/java/android/widget/SpellChecker.java15
-rw-r--r--core/java/android/widget/Spinner.java143
-rw-r--r--core/java/android/widget/TableLayout.java2
-rw-r--r--core/java/android/widget/TableRow.java2
-rw-r--r--core/java/android/widget/TextClock.java10
-rw-r--r--core/java/android/widget/TextView.java538
-rw-r--r--core/java/android/widget/VideoView.java115
-rw-r--r--core/java/com/android/internal/app/ActionBarImpl.java46
-rw-r--r--core/java/com/android/internal/app/IAppOpsCallback.aidl23
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl37
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl2
-rw-r--r--core/java/com/android/internal/app/LocalePicker.java45
-rw-r--r--core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java21
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetHost.aidl8
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetService.aidl47
-rw-r--r--core/java/com/android/internal/backup/IObbBackupService.aidl45
-rw-r--r--core/java/com/android/internal/content/PackageMonitor.java41
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodUtils.java913
-rw-r--r--core/java/com/android/internal/net/NetworkStatsFactory.java84
-rw-r--r--core/java/com/android/internal/os/BaseCommand.java146
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java316
-rw-r--r--core/java/com/android/internal/os/SomeArgs.java2
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java53
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java74
-rw-r--r--core/java/com/android/internal/policy/PolicyManager.java2
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl2
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl2
-rw-r--r--core/java/com/android/internal/statusbar/StatusBarNotification.java149
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java34
-rw-r--r--core/java/com/android/internal/util/FastXmlSerializer.java33
-rw-r--r--core/java/com/android/internal/util/IndentingPrintWriter.java77
-rw-r--r--core/java/com/android/internal/util/ProcFileReader.java50
-rw-r--r--core/java/com/android/internal/util/Protocol.java2
-rw-r--r--core/java/com/android/internal/util/StateMachine.java841
-rw-r--r--core/java/com/android/internal/util/XmlUtils.java56
-rw-r--r--core/java/com/android/internal/view/ActionBarPolicy.java6
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java2
-rw-r--r--core/java/com/android/internal/view/IInputMethod.aidl20
-rw-r--r--core/java/com/android/internal/view/IInputMethodSession.aidl9
-rw-r--r--core/java/com/android/internal/view/IInputSessionCallback.aidl (renamed from core/java/com/android/internal/view/IInputMethodCallback.aidl)15
-rw-r--r--core/java/com/android/internal/view/InputBindResult.java36
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItem.java2
-rw-r--r--core/java/com/android/internal/view/menu/MenuItemImpl.java12
-rw-r--r--core/java/com/android/internal/widget/ActionBarContainer.java11
-rw-r--r--core/java/com/android/internal/widget/ActionBarOverlayLayout.java239
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java143
-rw-r--r--core/java/com/android/internal/widget/DigitalClock.java245
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java34
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java159
-rw-r--r--core/java/com/android/internal/widget/LockSettingsService.java405
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java44
-rw-r--r--core/java/com/android/internal/widget/ScrollingTabContainerView.java28
-rw-r--r--core/java/com/android/internal/widget/SizeAdaptiveLayout.java12
-rw-r--r--core/java/com/android/internal/widget/TransportControlView.java5
437 files changed, 34735 insertions, 21492 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 9d6ee80..1e3d5be 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -24,6 +24,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -307,6 +308,8 @@ public abstract class AccessibilityService extends Service {
* android:accessibilityFlags="flagDefault"
* android:settingsActivity="foo.bar.TestBackActivity"
* android:canRetrieveWindowContent="true"
+ * android:canRequestTouchExplorationMode="true"
+ * android:canRequestEnhancedWebAccessibility="true"
* . . .
* /&gt;</pre>
*/
@@ -339,12 +342,16 @@ public abstract class AccessibilityService extends Service {
private static final String LOG_TAG = "AccessibilityService";
- interface Callbacks {
+ /**
+ * @hide
+ */
+ public interface Callbacks {
public void onAccessibilityEvent(AccessibilityEvent event);
public void onInterrupt();
public void onServiceConnected();
public void onSetConnectionId(int connectionId);
public boolean onGesture(int gestureId);
+ public boolean onKeyEvent(KeyEvent event);
}
private int mConnectionId;
@@ -410,6 +417,32 @@ public abstract class AccessibilityService extends Service {
}
/**
+ * Callback that allows an accessibility service to observe the key events
+ * before they are passed to the rest of the system. This means that the events
+ * are first delivered here before they are passed to the device policy, the
+ * input method, or applications.
+ * <p>
+ * <strong>Note:</strong> It is important that key events are handled in such
+ * a way that the event stream that would be passed to the rest of the system
+ * is well-formed. For example, handling the down event but not the up event
+ * and vice versa would generate an inconsistent event stream.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The key events delivered in this method are copies
+ * and modifying them will have no effect on the events that will be passed
+ * to the system. This method is intended to perform purely filtering
+ * functionality.
+ * <p>
+ *
+ * @param event The event to be processed.
+ * @return If true then the event will be consumed and not delivered to
+ * applications, otherwise it will be delivered as usual.
+ */
+ protected boolean onKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ /**
* Gets the root node in the currently active window if this service
* can retrieve window content.
*
@@ -454,7 +487,7 @@ public abstract class AccessibilityService extends Service {
*
* @return The accessibility service info.
*
- * @see AccessibilityNodeInfo
+ * @see AccessibilityServiceInfo
*/
public final AccessibilityServiceInfo getServiceInfo() {
IAccessibilityServiceConnection connection =
@@ -532,14 +565,21 @@ public abstract class AccessibilityService extends Service {
public boolean onGesture(int gestureId) {
return AccessibilityService.this.onGesture(gestureId);
}
+
+ @Override
+ public boolean onKeyEvent(KeyEvent event) {
+ return AccessibilityService.this.onKeyEvent(event);
+ }
});
}
/**
* Implements the internal {@link IAccessibilityServiceClient} interface to convert
* incoming calls to it back to calls on an {@link AccessibilityService}.
+ *
+ * @hide
*/
- static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
+ public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
implements HandlerCaller.Callback {
static final int NO_ID = -1;
@@ -548,11 +588,15 @@ public abstract class AccessibilityService extends Service {
private static final int DO_ON_INTERRUPT = 20;
private static final int DO_ON_ACCESSIBILITY_EVENT = 30;
private static final int DO_ON_GESTURE = 40;
+ private static final int DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 50;
+ private static final int DO_ON_KEY_EVENT = 60;
private final HandlerCaller mCaller;
private final Callbacks mCallback;
+ private int mConnectionId;
+
public IAccessibilityServiceClientWrapper(Context context, Looper looper,
Callbacks callback) {
mCallback = callback;
@@ -580,37 +624,70 @@ public abstract class AccessibilityService extends Service {
mCaller.sendMessage(message);
}
+ public void clearAccessibilityNodeInfoCache() {
+ Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE);
+ mCaller.sendMessage(message);
+ }
+
+ @Override
+ public void onKeyEvent(KeyEvent event, int sequence) {
+ Message message = mCaller.obtainMessageIO(DO_ON_KEY_EVENT, sequence, event);
+ mCaller.sendMessage(message);
+ }
+
public void executeMessage(Message message) {
switch (message.what) {
- case DO_ON_ACCESSIBILITY_EVENT :
+ case DO_ON_ACCESSIBILITY_EVENT: {
AccessibilityEvent event = (AccessibilityEvent) message.obj;
if (event != null) {
AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);
mCallback.onAccessibilityEvent(event);
event.recycle();
}
- return;
- case DO_ON_INTERRUPT :
+ } return;
+ case DO_ON_INTERRUPT: {
mCallback.onInterrupt();
- return;
- case DO_SET_SET_CONNECTION :
- final int connectionId = message.arg1;
+ } return;
+ case DO_SET_SET_CONNECTION: {
+ mConnectionId = message.arg1;
IAccessibilityServiceConnection connection =
(IAccessibilityServiceConnection) message.obj;
if (connection != null) {
- AccessibilityInteractionClient.getInstance().addConnection(connectionId,
+ AccessibilityInteractionClient.getInstance().addConnection(mConnectionId,
connection);
- mCallback.onSetConnectionId(connectionId);
+ mCallback.onSetConnectionId(mConnectionId);
mCallback.onServiceConnected();
} else {
- AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
+ AccessibilityInteractionClient.getInstance().removeConnection(mConnectionId);
+ AccessibilityInteractionClient.getInstance().clearCache();
mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID);
}
- return;
- case DO_ON_GESTURE :
+ } return;
+ case DO_ON_GESTURE: {
final int gestureId = message.arg1;
mCallback.onGesture(gestureId);
- return;
+ } return;
+ case DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: {
+ AccessibilityInteractionClient.getInstance().clearCache();
+ } return;
+ case DO_ON_KEY_EVENT: {
+ KeyEvent event = (KeyEvent) message.obj;
+ try {
+ IAccessibilityServiceConnection connection = AccessibilityInteractionClient
+ .getInstance().getConnection(mConnectionId);
+ if (connection != null) {
+ final boolean result = mCallback.onKeyEvent(event);
+ final int sequence = message.arg1;
+ try {
+ connection.setOnKeyEventResult(result, sequence);
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+ } finally {
+ event.recycle();
+ }
+ } return;
default :
Log.w(LOG_TAG, "Unknown message type " + message.what);
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 75a4f83..059945f 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -29,15 +29,22 @@ import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
+import android.util.SparseArray;
import android.util.TypedValue;
import android.util.Xml;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import com.android.internal.R;
+
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* This class describes an {@link AccessibilityService}. The system notifies an
@@ -51,6 +58,18 @@ import java.io.IOException;
* developer guide.</p>
* </div>
*
+ * @attr ref android.R.styleable#AccessibilityService_accessibilityEventTypes
+ * @attr ref android.R.styleable#AccessibilityService_accessibilityFeedbackType
+ * @attr ref android.R.styleable#AccessibilityService_accessibilityFlags
+ * @attr ref android.R.styleable#AccessibilityService_canRequestEnhancedWebAccessibility
+ * @attr ref android.R.styleable#AccessibilityService_canRequestFilterKeyEvents
+ * @attr ref android.R.styleable#AccessibilityService_canRequestTouchExplorationMode
+ * @attr ref android.R.styleable#AccessibilityService_canRetrieveWindowContent
+ * @attr ref android.R.styleable#AccessibilityService_description
+ * @attr ref android.R.styleable#AccessibilityService_notificationTimeout
+ * @attr ref android.R.styleable#AccessibilityService_packageNames
+ * @attr ref android.R.styleable#AccessibilityService_settingsActivity
+ *
* @see AccessibilityService
* @see android.view.accessibility.AccessibilityEvent
* @see android.view.accessibility.AccessibilityManager
@@ -60,6 +79,53 @@ public class AccessibilityServiceInfo implements Parcelable {
private static final String TAG_ACCESSIBILITY_SERVICE = "accessibility-service";
/**
+ * Capability: This accessibility service can retrieve the active window content.
+ * @see android.R.styleable#AccessibilityService_canRetrieveWindowContent
+ */
+ public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 0x00000001;
+
+ /**
+ * Capability: This accessibility service can request touch exploration mode in which
+ * touched items are spoken aloud and the UI can be explored via gestures.
+ * @see android.R.styleable#AccessibilityService_canRequestTouchExplorationMode
+ */
+ public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 0x00000002;
+
+ /**
+ * Capability: This accessibility service can request enhanced web accessibility
+ * enhancements. For example, installing scripts to make app content more accessible.
+ * @see android.R.styleable#AccessibilityService_canRequestEnhancedWebAccessibility
+ */
+ public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000004;
+
+ /**
+ * Capability: This accessibility service can request to filter the key event stream.
+ * @see android.R.styleable#AccessibilityService_canRequestFilterKeyEvents
+ */
+ public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 0x00000008;
+
+ private static final SparseArray<CapabilityInfo> sAvailableCapabilityInfos =
+ new SparseArray<CapabilityInfo>();
+ static {
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
+ new CapabilityInfo(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
+ R.string.capability_title_canRetrieveWindowContent,
+ R.string.capability_desc_canRetrieveWindowContent));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
+ new CapabilityInfo(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
+ R.string.capability_title_canRequestTouchExploration,
+ R.string.capability_desc_canRequestTouchExploration));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY,
+ new CapabilityInfo(CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY,
+ R.string.capability_title_canRequestEnhancedWebAccessibility,
+ R.string.capability_desc_canRequestEnhancedWebAccessibility));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
+ new CapabilityInfo(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
+ R.string.capability_title_canRequestFilterKeyEvents,
+ R.string.capability_desc_canRequestFilterKeyEvents));
+ }
+
+ /**
* Denotes spoken feedback.
*/
public static final int FEEDBACK_SPOKEN = 0x0000001;
@@ -148,8 +214,73 @@ public class AccessibilityServiceInfo implements Parcelable {
* accessibility service that has this flag set. Hence, clearing this
* flag does not guarantee that the device will not be in touch exploration
* mode since there may be another enabled service that requested it.
+ * <p>
+ * For accessibility services targeting API version higher than
+ * {@link Build.VERSION_CODES#JELLY_BEAN_MR1} that want to set
+ * this flag have to declare this capability in their meta-data by setting
+ * the attribute {@link android.R.attr#canRequestTouchExplorationMode
+ * canRequestTouchExplorationMode} to true, otherwise this flag will
+ * be ignored. For how to declare the meta-data of a service refer to
+ * {@value AccessibilityService#SERVICE_META_DATA}.
+ * </p>
+ * <p>
+ * Services targeting API version equal to or lower than
+ * {@link Build.VERSION_CODES#JELLY_BEAN_MR1} will work normally, i.e.
+ * the first time they are run, if this flag is specified, a dialog is
+ * shown to the user to confirm enabling explore by touch.
+ * </p>
+ * @see android.R.styleable#AccessibilityService_canRequestTouchExplorationMode
+ */
+ public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 0x0000004;
+
+ /**
+ * This flag requests from the system to enable web accessibility enhancing
+ * extensions. Such extensions aim to provide improved accessibility support
+ * for content presented in a {@link android.webkit.WebView}. An example of such
+ * an extension is injecting JavaScript from a secure source. The system will enable
+ * enhanced web accessibility if there is at least one accessibility service
+ * that has this flag set. Hence, clearing this flag does not guarantee that the
+ * device will not have enhanced web accessibility enabled since there may be
+ * another enabled service that requested it.
+ * <p>
+ * Services that want to set this flag have to declare this capability
+ * in their meta-data by setting the attribute {@link android.R.attr
+ * #canRequestEnhancedWebAccessibility canRequestEnhancedWebAccessibility} to
+ * true, otherwise this flag will be ignored. For how to declare the meta-data
+ * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
+ * </p>
+ * @see android.R.styleable#AccessibilityService_canRequestEnhancedWebAccessibility
*/
- public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE= 0x0000004;
+ public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000008;
+
+ /**
+ * This flag requests that the {@link AccessibilityNodeInfo}s obtained
+ * by an {@link AccessibilityService} contain the id of the source view.
+ * The source view id will be a fully qualified resource name of the
+ * form "package:id/name", for example "foo.bar:id/my_list", and it is
+ * useful for UI test automation. This flag is not set by default.
+ */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
+
+ /**
+ * This flag requests from the system to filter key events. If this flag
+ * is set the accessibility service will receive the key events before
+ * applications allowing it implement global shortcuts. Setting this flag
+ * does not guarantee that this service will filter key events since only
+ * one service can do so at any given time. This avoids user confusion due
+ * to behavior change in case different key filtering services are enabled.
+ * If there is already another key filtering service enabled, this one will
+ * not receive key events.
+ * <p>
+ * Services that want to set this flag have to declare this capability
+ * in their meta-data by setting the attribute {@link android.R.attr
+ * #canRequestFilterKeyEvents canRequestFilterKeyEvents} to true,
+ * otherwise this flag will be ignored. For how to declare the meta-data
+ * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
+ * </p>
+ * @see android.R.styleable#AccessibilityService_canRequestFilterKeyEvents
+ */
+ public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 0x00000020;
/**
* The event types an {@link AccessibilityService} is interested in.
@@ -219,6 +350,9 @@ public class AccessibilityServiceInfo implements Parcelable {
* @see #DEFAULT
* @see #FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
* @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ * @see #FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY
+ * @see #FLAG_REQUEST_FILTER_KEY_EVENTS
+ * @see #FLAG_REPORT_VIEW_IDS
*/
public int flags;
@@ -239,9 +373,9 @@ public class AccessibilityServiceInfo implements Parcelable {
private String mSettingsActivityName;
/**
- * Flag whether this accessibility service can retrieve window content.
+ * Bit mask with capabilities of this service.
*/
- private boolean mCanRetrieveWindowContent;
+ private int mCapabilities;
/**
* Resource id of the description of the accessibility service.
@@ -320,9 +454,22 @@ public class AccessibilityServiceInfo implements Parcelable {
com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
mSettingsActivityName = asAttributes.getString(
com.android.internal.R.styleable.AccessibilityService_settingsActivity);
- mCanRetrieveWindowContent = asAttributes.getBoolean(
- com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent,
- false);
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canRetrieveWindowContent, false)) {
+ mCapabilities |= CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canRequestTouchExplorationMode, false)) {
+ mCapabilities |= CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canRequestEnhancedWebAccessibility, false)) {
+ mCapabilities |= CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY;
+ }
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canRequestFilterKeyEvents, false)) {
+ mCapabilities |= CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
+ }
TypedValue peekedValue = asAttributes.peekValue(
com.android.internal.R.styleable.AccessibilityService_description);
if (peekedValue != null) {
@@ -359,6 +506,13 @@ public class AccessibilityServiceInfo implements Parcelable {
}
/**
+ * @hide
+ */
+ public void setComponentName(ComponentName component) {
+ mId = component.flattenToShortString();
+ }
+
+ /**
* The accessibility service id.
* <p>
* <strong>Generated by the system.</strong>
@@ -399,9 +553,43 @@ public class AccessibilityServiceInfo implements Parcelable {
* {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
* </p>
* @return True if window content can be retrieved.
+ *
+ * @deprecated Use {@link #getCapabilities()}.
*/
public boolean getCanRetrieveWindowContent() {
- return mCanRetrieveWindowContent;
+ return (mCapabilities & CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
+ }
+
+ /**
+ * Returns the bit mask of capabilities this accessibility service has such as
+ * being able to retrieve the active window content, etc.
+ *
+ * @return The capability bit mask.
+ *
+ * @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
+ * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+ * @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
+ * @see #CAPABILITY_FILTER_KEY_EVENTS
+ */
+ public int getCapabilities() {
+ return mCapabilities;
+ }
+
+ /**
+ * Sets the bit mask of capabilities this accessibility service has such as
+ * being able to retrieve the active window content, etc.
+ *
+ * @param capabilities The capability bit mask.
+ *
+ * @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
+ * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+ * @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
+ * @see #CAPABILITY_FILTER_KEY_EVENTS
+ *
+ * @hide
+ */
+ public void setCapabilities(int capabilities) {
+ mCapabilities = capabilities;
}
/**
@@ -455,7 +643,7 @@ public class AccessibilityServiceInfo implements Parcelable {
parcel.writeString(mId);
parcel.writeParcelable(mResolveInfo, 0);
parcel.writeString(mSettingsActivityName);
- parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0);
+ parcel.writeInt(mCapabilities);
parcel.writeInt(mDescriptionResId);
parcel.writeString(mNonLocalizedDescription);
}
@@ -469,12 +657,39 @@ public class AccessibilityServiceInfo implements Parcelable {
mId = parcel.readString();
mResolveInfo = parcel.readParcelable(null);
mSettingsActivityName = parcel.readString();
- mCanRetrieveWindowContent = (parcel.readInt() == 1);
+ mCapabilities = parcel.readInt();
mDescriptionResId = parcel.readInt();
mNonLocalizedDescription = parcel.readString();
}
@Override
+ public int hashCode() {
+ return 31 * 1 + ((mId == null) ? 0 : mId.hashCode());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ AccessibilityServiceInfo other = (AccessibilityServiceInfo) obj;
+ if (mId == null) {
+ if (other.mId != null) {
+ return false;
+ }
+ } else if (!mId.equals(other.mId)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
appendEventTypes(stringBuilder, eventTypes);
@@ -493,7 +708,7 @@ public class AccessibilityServiceInfo implements Parcelable {
stringBuilder.append(", ");
stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName);
stringBuilder.append(", ");
- stringBuilder.append("retrieveScreenContent: ").append(mCanRetrieveWindowContent);
+ appendCapabilities(stringBuilder, mCapabilities);
return stringBuilder.toString();
}
@@ -554,6 +769,20 @@ public class AccessibilityServiceInfo implements Parcelable {
stringBuilder.append("]");
}
+ private static void appendCapabilities(StringBuilder stringBuilder, int capabilities) {
+ stringBuilder.append("capabilities:");
+ stringBuilder.append("[");
+ while (capabilities != 0) {
+ final int capabilityBit = (1 << Integer.numberOfTrailingZeros(capabilities));
+ stringBuilder.append(capabilityToString(capabilityBit));
+ capabilities &= ~capabilityBit;
+ if (capabilities != 0) {
+ stringBuilder.append(", ");
+ }
+ }
+ stringBuilder.append("]");
+ }
+
/**
* Returns the string representation of a feedback type. For example,
* {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN.
@@ -625,12 +854,77 @@ public class AccessibilityServiceInfo implements Parcelable {
return "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS";
case FLAG_REQUEST_TOUCH_EXPLORATION_MODE:
return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE";
+ case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
+ return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
+ case FLAG_REPORT_VIEW_IDS:
+ return "FLAG_REPORT_VIEW_IDS";
+ case FLAG_REQUEST_FILTER_KEY_EVENTS:
+ return "FLAG_REQUEST_FILTER_KEY_EVENTS";
default:
return null;
}
}
/**
+ * Returns the string representation of a capability. For example,
+ * {@link #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT} is represented
+ * by the string CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT.
+ *
+ * @param capability The capability.
+ * @return The string representation.
+ */
+ public static String capabilityToString(int capability) {
+ switch (capability) {
+ case CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT:
+ return "CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT";
+ case CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION:
+ return "CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION";
+ case CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
+ return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
+ case CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS:
+ return "CAPABILITY_CAN_FILTER_KEY_EVENTS";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /**
+ * @hide
+ * @return The list of {@link CapabilityInfo} objects.
+ */
+ public List<CapabilityInfo> getCapabilityInfos() {
+ if (mCapabilities == 0) {
+ return Collections.emptyList();
+ }
+ int capabilities = mCapabilities;
+ List<CapabilityInfo> capabilityInfos = new ArrayList<CapabilityInfo>();
+ while (capabilities != 0) {
+ final int capabilityBit = 1 << Integer.numberOfTrailingZeros(capabilities);
+ capabilities &= ~capabilityBit;
+ CapabilityInfo capabilityInfo = sAvailableCapabilityInfos.get(capabilityBit);
+ if (capabilityInfo != null) {
+ capabilityInfos.add(capabilityInfo);
+ }
+ }
+ return capabilityInfos;
+ }
+
+ /**
+ * @hide
+ */
+ public static final class CapabilityInfo {
+ public final int capability;
+ public final int titleResId;
+ public final int descResId;
+
+ public CapabilityInfo(int capability, int titleResId, int descResId) {
+ this.capability = capability;
+ this.titleResId = titleResId;
+ this.descResId = descResId;
+ }
+ }
+
+ /**
* @see Parcelable.Creator
*/
public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index d459fd5..c5e3d43a 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -18,6 +18,7 @@ package android.accessibilityservice;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.view.accessibility.AccessibilityEvent;
+import android.view.KeyEvent;
/**
* Top-level interface to an accessibility service component.
@@ -33,4 +34,8 @@ import android.view.accessibility.AccessibilityEvent;
void onInterrupt();
void onGesture(int gesture);
+
+ void clearAccessibilityNodeInfoCache();
+
+ void onKeyEvent(in KeyEvent event, int sequence);
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index dd50f3c..3df06b5 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,6 +18,7 @@ package android.accessibilityservice;
import android.os.Bundle;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.view.MagnificationSpec;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
@@ -30,144 +31,31 @@ interface IAccessibilityServiceConnection {
void setServiceInfo(in AccessibilityServiceInfo info);
- /**
- * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} by accessibility id.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
- * to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
- * to start from the root.
- * @param interactionId The id of the interaction for matching with the callback result.
- * @param callback Callback which to receive the result.
- * @param flags Additional flags.
- * @param threadId The id of the calling thread.
- * @return The current window scale, where zero means a failure.
- */
- float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+ boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, long threadId);
- /**
- * Finds {@link android.view.accessibility.AccessibilityNodeInfo}s by View text.
- * The match is case insensitive containment. The search is performed in the window
- * whose id is specified and starts from the node whose accessibility id is specified.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
- * to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
- * to start from the root.
- * @param text The searched text.
- * @param interactionId The id of the interaction for matching with the callback result.
- * @param callback Callback which to receive the result.
- * @param threadId The id of the calling thread.
- * @return The current window scale, where zero means a failure.
- */
- float findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
+ boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
long threadId);
- /**
- * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} by View id. The search
- * is performed in the window whose id is specified and starts from the node whose
- * accessibility id is specified.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
- * to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
- * to start from the root.
- * @param id The id of the node.
- * @param interactionId The id of the interaction for matching with the callback result.
- * @param callback Callback which to receive the result.
- * @param threadId The id of the calling thread.
- * @return The current window scale, where zero means a failure.
- */
- float findAccessibilityNodeInfoByViewId(int accessibilityWindowId, long accessibilityNodeId,
- int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- long threadId);
+ boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+ long accessibilityNodeId, String viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long threadId);
- /**
- * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the specified
- * focus type. The search is performed in the window whose id is specified and starts from
- * the node whose accessibility id is specified.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
- * to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
- * to start from the root.
- * @param focusType The type of focus to find.
- * @param interactionId The id of the interaction for matching with the callback result.
- * @param callback Callback which to receive the result.
- * @param threadId The id of the calling thread.
- * @return The current window scale, where zero means a failure.
- */
- float findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
+ boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
- /**
- * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} to take accessibility
- * focus in the given direction. The search is performed in the window whose id is
- * specified and starts from the node whose accessibility id is specified.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
- * to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
- * to start from the root.
- * @param direction The direction in which to search for focusable.
- * @param interactionId The id of the interaction for matching with the callback result.
- * @param callback Callback which to receive the result.
- * @param threadId The id of the calling thread.
- * @return The current window scale, where zero means a failure.
- */
- float focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
+ boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
- /**
- * Performs an accessibility action on an
- * {@link android.view.accessibility.AccessibilityNodeInfo}.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
- * to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use
- * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
- * to start from the root.
- * @param action The action to perform.
- * @param arguments Optional action arguments.
- * @param interactionId The id of the interaction for matching with the callback result.
- * @param callback Callback which to receive the result.
- * @param threadId The id of the calling thread.
- * @return Whether the action was performed.
- */
boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
int action, in Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long threadId);
- /**
- * @return The associated accessibility service info.
- */
AccessibilityServiceInfo getServiceInfo();
- /**
- * Performs a global action, such as going home, going back, etc.
- *
- * @param action The action to perform.
- * @return Whether the action was performed.
- */
boolean performGlobalAction(int action);
+
+ oneway void setOnKeyEventResult(boolean handled, int sequence);
}
diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
deleted file mode 100644
index 6837386..0000000
--- a/core/java/android/accessibilityservice/UiTestAutomationBridge.java
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accessibilityservice;
-
-import android.accessibilityservice.AccessibilityService.Callbacks;
-import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.IAccessibilityManager;
-
-import com.android.internal.util.Predicate;
-
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-
-/**
- * This class represents a bridge that can be used for UI test
- * automation. It is responsible for connecting to the system,
- * keeping track of the last accessibility event, and exposing
- * window content querying APIs. This class is designed to be
- * used from both an Android application and a Java program
- * run from the shell.
- *
- * @hide
- */
-public class UiTestAutomationBridge {
-
- private static final String LOG_TAG = UiTestAutomationBridge.class.getSimpleName();
-
- private static final int TIMEOUT_REGISTER_SERVICE = 5000;
-
- public static final int ACTIVE_WINDOW_ID = AccessibilityNodeInfo.ACTIVE_WINDOW_ID;
-
- public static final long ROOT_NODE_ID = AccessibilityNodeInfo.ROOT_NODE_ID;
-
- public static final int UNDEFINED = -1;
-
- private static final int FIND_ACCESSIBILITY_NODE_INFO_PREFETCH_FLAGS =
- AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
- | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
- | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
-
- private final Object mLock = new Object();
-
- private volatile int mConnectionId = AccessibilityInteractionClient.NO_ID;
-
- private IAccessibilityServiceClientWrapper mListener;
-
- private AccessibilityEvent mLastEvent;
-
- private volatile boolean mWaitingForEventDelivery;
-
- private volatile boolean mUnprocessedEventAvailable;
-
- private HandlerThread mHandlerThread;
-
- /**
- * Gets the last received {@link AccessibilityEvent}.
- *
- * @return The event.
- */
- public AccessibilityEvent getLastAccessibilityEvent() {
- return mLastEvent;
- }
-
- /**
- * Callback for receiving an {@link AccessibilityEvent}.
- *
- * <strong>Note:</strong> This method is <strong>NOT</strong>
- * executed on the application main thread. The client are
- * responsible for proper synchronization.
- *
- * @param event The received event.
- */
- public void onAccessibilityEvent(AccessibilityEvent event) {
- /* hook - do nothing */
- }
-
- /**
- * Callback for requests to stop feedback.
- *
- * <strong>Note:</strong> This method is <strong>NOT</strong>
- * executed on the application main thread. The client are
- * responsible for proper synchronization.
- */
- public void onInterrupt() {
- /* hook - do nothing */
- }
-
- /**
- * Connects this service.
- *
- * @throws IllegalStateException If already connected.
- */
- public void connect() {
- if (isConnected()) {
- throw new IllegalStateException("Already connected.");
- }
-
- // Serialize binder calls to a handler on a dedicated thread
- // different from the main since we expose APIs that block
- // the main thread waiting for a result the deliver of which
- // on the main thread will prevent that thread from waking up.
- // The serialization is needed also to ensure that events are
- // examined in delivery order. Otherwise, a fair locking
- // is needed for making sure the binder calls are interleaved
- // with check for the expected event and also to make sure the
- // binder threads are allowed to proceed in the received order.
- mHandlerThread = new HandlerThread("UiTestAutomationBridge");
- mHandlerThread.setDaemon(true);
- mHandlerThread.start();
- Looper looper = mHandlerThread.getLooper();
-
- mListener = new IAccessibilityServiceClientWrapper(null, looper, new Callbacks() {
- @Override
- public void onServiceConnected() {
- /* do nothing */
- }
-
- @Override
- public void onInterrupt() {
- UiTestAutomationBridge.this.onInterrupt();
- }
-
- @Override
- public void onAccessibilityEvent(AccessibilityEvent event) {
- synchronized (mLock) {
- while (true) {
- mLastEvent = AccessibilityEvent.obtain(event);
- if (!mWaitingForEventDelivery) {
- mLock.notifyAll();
- break;
- }
- if (!mUnprocessedEventAvailable) {
- mUnprocessedEventAvailable = true;
- mLock.notifyAll();
- break;
- }
- try {
- mLock.wait();
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- UiTestAutomationBridge.this.onAccessibilityEvent(event);
- }
-
- @Override
- public void onSetConnectionId(int connectionId) {
- synchronized (mLock) {
- mConnectionId = connectionId;
- mLock.notifyAll();
- }
- }
-
- @Override
- public boolean onGesture(int gestureId) {
- return false;
- }
- });
-
- final IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
- ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
-
- final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
- info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
- info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
- info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-
- try {
- manager.registerUiTestAutomationService(mListener, info);
- } catch (RemoteException re) {
- throw new IllegalStateException("Cound not register UiAutomationService.", re);
- }
-
- synchronized (mLock) {
- final long startTimeMillis = SystemClock.uptimeMillis();
- while (true) {
- if (isConnected()) {
- return;
- }
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = TIMEOUT_REGISTER_SERVICE - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- throw new IllegalStateException("Cound not register UiAutomationService.");
- }
- try {
- mLock.wait(remainingTimeMillis);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Disconnects this service.
- *
- * @throws IllegalStateException If already disconnected.
- */
- public void disconnect() {
- if (!isConnected()) {
- throw new IllegalStateException("Already disconnected.");
- }
-
- mHandlerThread.quit();
-
- IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
- ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
-
- try {
- manager.unregisterUiTestAutomationService(mListener);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while unregistering UiTestAutomationService", re);
- }
- }
-
- /**
- * Gets whether this service is connected.
- *
- * @return True if connected.
- */
- public boolean isConnected() {
- return (mConnectionId != AccessibilityInteractionClient.NO_ID);
- }
-
- /**
- * Executes a command and waits for a specific accessibility event type up
- * to a given timeout.
- *
- * @param command The command to execute before starting to wait for the event.
- * @param predicate Predicate for recognizing the awaited event.
- * @param timeoutMillis The max wait time in milliseconds.
- */
- public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command,
- Predicate<AccessibilityEvent> predicate, long timeoutMillis)
- throws TimeoutException, Exception {
- // TODO: This is broken - remove from here when finalizing this as public APIs.
- synchronized (mLock) {
- // Prepare to wait for an event.
- mWaitingForEventDelivery = true;
- mUnprocessedEventAvailable = false;
- if (mLastEvent != null) {
- mLastEvent.recycle();
- mLastEvent = null;
- }
- // Execute the command.
- command.run();
- // Wait for the event.
- final long startTimeMillis = SystemClock.uptimeMillis();
- while (true) {
- // If the expected event is received, that's it.
- if ((mUnprocessedEventAvailable && predicate.apply(mLastEvent))) {
- mWaitingForEventDelivery = false;
- mUnprocessedEventAvailable = false;
- mLock.notifyAll();
- return mLastEvent;
- }
- // Ask for another event.
- mWaitingForEventDelivery = true;
- mUnprocessedEventAvailable = false;
- mLock.notifyAll();
- // Check if timed out and if not wait.
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- mWaitingForEventDelivery = false;
- mUnprocessedEventAvailable = false;
- mLock.notifyAll();
- throw new TimeoutException("Expacted event not received within: "
- + timeoutMillis + " ms.");
- }
- try {
- mLock.wait(remainingTimeMillis);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Waits for the accessibility event stream to become idle, which is not to
- * have received a new accessibility event within <code>idleTimeout</code>,
- * and do so within a maximal global timeout as specified by
- * <code>globalTimeout</code>.
- *
- * @param idleTimeout The timeout between two event to consider the device idle.
- * @param globalTimeout The maximal global timeout in which to wait for idle.
- */
- public void waitForIdle(long idleTimeout, long globalTimeout) {
- final long startTimeMillis = SystemClock.uptimeMillis();
- long lastEventTime = (mLastEvent != null)
- ? mLastEvent.getEventTime() : SystemClock.uptimeMillis();
- synchronized (mLock) {
- while (true) {
- final long currentTimeMillis = SystemClock.uptimeMillis();
- final long sinceLastEventTimeMillis = currentTimeMillis - lastEventTime;
- if (sinceLastEventTimeMillis > idleTimeout) {
- return;
- }
- if (mLastEvent != null) {
- lastEventTime = mLastEvent.getEventTime();
- }
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = globalTimeout - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- return;
- }
- try {
- mLock.wait(idleTimeout);
- } catch (InterruptedException e) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by accessibility id in the active
- * window. The search is performed from the root node.
- *
- * @param accessibilityNodeId A unique view id or virtual descendant id for
- * which to search.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityIdInActiveWindow(
- long accessibilityNodeId) {
- return findAccessibilityNodeInfoByAccessibilityId(ACTIVE_WINDOW_ID, accessibilityNodeId);
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by accessibility id.
- *
- * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID} to query
- * the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id for
- * which to search.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
- int accessibilityWindowId, long accessibilityNodeId) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
- accessibilityWindowId, accessibilityNodeId,
- FIND_ACCESSIBILITY_NODE_INFO_PREFETCH_FLAGS);
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by View id in the active
- * window. The search is performed from the root node.
- *
- * @param viewId The id of a View.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId) {
- return findAccessibilityNodeInfoByViewId(ACTIVE_WINDOW_ID, ROOT_NODE_ID, viewId);
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
- * the window whose id is specified and starts from the node whose accessibility
- * id is specified.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
- * @param viewId The id of a View.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
- long accessibilityNodeId, int viewId) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewId(connectionId, accessibilityWindowId,
- accessibilityNodeId, viewId);
- }
-
- /**
- * Finds {@link AccessibilityNodeInfo}s by View text in the active
- * window. The search is performed from the root node.
- *
- * @param text The searched text.
- * @return The current window scale, where zero means a failure.
- */
- public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindow(String text) {
- return findAccessibilityNodeInfosByText(ACTIVE_WINDOW_ID, ROOT_NODE_ID, text);
- }
-
- /**
- * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
- * insensitive containment. The search is performed in the window whose
- * id is specified and starts from the node whose accessibility id is
- * specified.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
- * @param text The searched text.
- * @return The current window scale, where zero means a failure.
- */
- public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int accessibilityWindowId,
- long accessibilityNodeId, String text) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfosByText(connectionId, accessibilityWindowId,
- accessibilityNodeId, text);
- }
-
- /**
- * Performs an accessibility action on an {@link AccessibilityNodeInfo}
- * in the active window.
- *
- * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
- * @param action The action to perform.
- * @param arguments Optional action arguments.
- * @return Whether the action was performed.
- */
- public boolean performAccessibilityActionInActiveWindow(long accessibilityNodeId, int action,
- Bundle arguments) {
- return performAccessibilityAction(ACTIVE_WINDOW_ID, accessibilityNodeId, action, arguments);
- }
-
- /**
- * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
- * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
- * @param action The action to perform.
- * @param arguments Optional action arguments.
- * @return Whether the action was performed.
- */
- public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
- int action, Bundle arguments) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance().performAccessibilityAction(connectionId,
- accessibilityWindowId, accessibilityNodeId, action, arguments);
- }
-
- /**
- * Gets the root {@link AccessibilityNodeInfo} in the active window.
- *
- * @return The root info.
- */
- public AccessibilityNodeInfo getRootAccessibilityNodeInfoInActiveWindow() {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByAccessibilityId(connectionId, ACTIVE_WINDOW_ID,
- ROOT_NODE_ID, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
- }
-
- private void ensureValidConnection(int connectionId) {
- if (connectionId == UNDEFINED) {
- throw new IllegalStateException("UiAutomationService not connected."
- + " Did you call #register()?");
- }
- }
-}
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index e9535ab..dbc9051 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -275,6 +275,38 @@ public abstract class AbstractAccountAuthenticator {
handleException(response, "getAccountRemovalAllowed", account.toString(), e);
}
}
+
+ public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response,
+ Account account) throws RemoteException {
+ checkBinderPermission();
+ try {
+ final Bundle result =
+ AbstractAccountAuthenticator.this.getAccountCredentialsForCloning(
+ new AccountAuthenticatorResponse(response), account);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "getAccountCredentialsForCloning", account.toString(), e);
+ }
+ }
+
+ public void addAccountFromCredentials(IAccountAuthenticatorResponse response,
+ Account account,
+ Bundle accountCredentials) throws RemoteException {
+ checkBinderPermission();
+ try {
+ final Bundle result =
+ AbstractAccountAuthenticator.this.addAccountFromCredentials(
+ new AccountAuthenticatorResponse(response), account,
+ accountCredentials);
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "addAccountFromCredentials", account.toString(), e);
+ }
+ }
}
private void handleException(IAccountAuthenticatorResponse response, String method,
@@ -471,4 +503,52 @@ public abstract class AbstractAccountAuthenticator {
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
return result;
}
+
+ /**
+ * Returns a Bundle that contains whatever is required to clone the account on a different
+ * user. The Bundle is passed to the authenticator instance in the target user via
+ * {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}.
+ * The default implementation returns null, indicating that cloning is not supported.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to clone, will never be null
+ * @return a Bundle result or null if the result is to be returned via the response.
+ * @throws NetworkErrorException
+ * @see {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}
+ */
+ public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response,
+ final Account account) throws NetworkErrorException {
+ new Thread(new Runnable() {
+ public void run() {
+ Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ response.onResult(result);
+ }
+ }).start();
+ return null;
+ }
+
+ /**
+ * Creates an account based on credentials provided by the authenticator instance of another
+ * user on the device, who has chosen to share the account with this user.
+ * @param response to send the result back to the AccountManager, will never be null
+ * @param account the account to clone, will never be null
+ * @param accountCredentials the Bundle containing the required credentials to create the
+ * account. Contents of the Bundle are only meaningful to the authenticator. This Bundle is
+ * provided by {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}.
+ * @return a Bundle result or null if the result is to be returned via the response.
+ * @throws NetworkErrorException
+ * @see {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}
+ */
+ public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response,
+ Account account,
+ Bundle accountCredentials) throws NetworkErrorException {
+ new Thread(new Runnable() {
+ public void run() {
+ Bundle result = new Bundle();
+ result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+ response.onResult(result);
+ }
+ }).start();
+ return null;
+ }
}
diff --git a/core/java/android/accounts/AccountAuthenticatorCache.java b/core/java/android/accounts/AccountAuthenticatorCache.java
deleted file mode 100644
index f937cde..0000000
--- a/core/java/android/accounts/AccountAuthenticatorCache.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accounts;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.RegisteredServicesCache;
-import android.content.pm.XmlSerializerAndParser;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-
-/**
- * A cache of services that export the {@link IAccountAuthenticator} interface. This cache
- * is built by interrogating the {@link PackageManager} and is updated as packages are added,
- * removed and changed. The authenticators are referred to by their account type and
- * are made available via the {@link RegisteredServicesCache#getServiceInfo} method.
- * @hide
- */
-/* package private */ class AccountAuthenticatorCache
- extends RegisteredServicesCache<AuthenticatorDescription>
- implements IAccountAuthenticatorCache {
- private static final String TAG = "Account";
- private static final MySerializer sSerializer = new MySerializer();
-
- public AccountAuthenticatorCache(Context context) {
- super(context, AccountManager.ACTION_AUTHENTICATOR_INTENT,
- AccountManager.AUTHENTICATOR_META_DATA_NAME,
- AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer);
- }
-
- public AuthenticatorDescription parseServiceAttributes(Resources res,
- String packageName, AttributeSet attrs) {
- TypedArray sa = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.AccountAuthenticator);
- try {
- final String accountType =
- sa.getString(com.android.internal.R.styleable.AccountAuthenticator_accountType);
- final int labelId = sa.getResourceId(
- com.android.internal.R.styleable.AccountAuthenticator_label, 0);
- final int iconId = sa.getResourceId(
- com.android.internal.R.styleable.AccountAuthenticator_icon, 0);
- final int smallIconId = sa.getResourceId(
- com.android.internal.R.styleable.AccountAuthenticator_smallIcon, 0);
- final int prefId = sa.getResourceId(
- com.android.internal.R.styleable.AccountAuthenticator_accountPreferences, 0);
- final boolean customTokens = sa.getBoolean(
- com.android.internal.R.styleable.AccountAuthenticator_customTokens, false);
- if (TextUtils.isEmpty(accountType)) {
- return null;
- }
- return new AuthenticatorDescription(accountType, packageName, labelId, iconId,
- smallIconId, prefId, customTokens);
- } finally {
- sa.recycle();
- }
- }
-
- private static class MySerializer implements XmlSerializerAndParser<AuthenticatorDescription> {
- public void writeAsXml(AuthenticatorDescription item, XmlSerializer out)
- throws IOException {
- out.attribute(null, "type", item.type);
- }
-
- public AuthenticatorDescription createFromXml(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- return AuthenticatorDescription.newKey(parser.getAttributeValue(null, "type"));
- }
- }
-}
diff --git a/core/java/android/accounts/AccountAuthenticatorResponse.java b/core/java/android/accounts/AccountAuthenticatorResponse.java
index 614e139..41f26ac 100644
--- a/core/java/android/accounts/AccountAuthenticatorResponse.java
+++ b/core/java/android/accounts/AccountAuthenticatorResponse.java
@@ -33,7 +33,7 @@ public class AccountAuthenticatorResponse implements Parcelable {
/**
* @hide
*/
- /* package private */ AccountAuthenticatorResponse(IAccountAuthenticatorResponse response) {
+ public AccountAuthenticatorResponse(IAccountAuthenticatorResponse response) {
mAccountAuthenticatorResponse = response;
}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 03a7e34..12fcdcf 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -18,9 +18,11 @@ package android.accounts;
import android.app.Activity;
import android.content.Intent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
+import android.content.res.Resources;
import android.database.SQLException;
import android.os.Bundle;
import android.os.Handler;
@@ -44,6 +46,7 @@ import java.util.concurrent.TimeUnit;
import java.util.HashMap;
import java.util.Map;
+import com.android.internal.R;
import com.google.android.collect.Maps;
/**
@@ -148,6 +151,10 @@ public class AccountManager {
public static final int ERROR_CODE_UNSUPPORTED_OPERATION = 6;
public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
public static final int ERROR_CODE_BAD_REQUEST = 8;
+ public static final int ERROR_CODE_BAD_AUTHENTICATION = 9;
+
+ /** @hide */
+ public static final int ERROR_CODE_USER_RESTRICTED = 100;
/**
* Bundle key used for the {@link String} account name in results
@@ -381,6 +388,40 @@ public class AccountManager {
}
/**
+ * @hide
+ * For use by internal activities. Returns the list of accounts that the calling package
+ * is authorized to use, particularly for shared accounts.
+ * @param packageName package name of the calling app.
+ * @param uid the uid of the calling app.
+ * @return the accounts that are available to this package and user.
+ */
+ public Account[] getAccountsForPackage(String packageName, int uid) {
+ try {
+ return mService.getAccountsForPackage(packageName, uid);
+ } catch (RemoteException re) {
+ // possible security exception
+ throw new RuntimeException(re);
+ }
+ }
+
+ /**
+ * Returns the accounts visible to the specified package, in an environment where some apps
+ * are not authorized to view all accounts. This method can only be called by system apps.
+ * @param type The type of accounts to return, null to retrieve all accounts
+ * @param packageName The package name of the app for which the accounts are to be returned
+ * @return An array of {@link Account}, one per matching account. Empty
+ * (never null) if no accounts of the specified type have been added.
+ */
+ public Account[] getAccountsByTypeForPackage(String type, String packageName) {
+ try {
+ return mService.getAccountsByTypeForPackage(type, packageName);
+ } catch (RemoteException re) {
+ // possible security exception
+ throw new RuntimeException(re);
+ }
+ }
+
+ /**
* Lists all accounts of a particular type. The account type is a
* string token corresponding to the authenticator and useful domain
* of the account. For example, there are types corresponding to Google
@@ -568,7 +609,7 @@ public class AccountManager {
public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
if (account == null) throw new IllegalArgumentException("account is null");
try {
- return mService.addAccount(account, password, userdata);
+ return mService.addAccountExplicitly(account, password, userdata);
} catch (RemoteException e) {
// won't ever happen
throw new RuntimeException(e);
@@ -958,10 +999,10 @@ public class AccountManager {
*/
@Deprecated
public AccountManagerFuture<Bundle> getAuthToken(
- final Account account, final String authTokenType,
+ final Account account, final String authTokenType,
final boolean notifyAuthFailure,
AccountManagerCallback<Bundle> callback, Handler handler) {
- return getAuthToken(account, authTokenType, null, notifyAuthFailure, callback,
+ return getAuthToken(account, authTokenType, null, notifyAuthFailure, callback,
handler);
}
@@ -1116,13 +1157,64 @@ public class AccountManager {
return new AmsTask(activity, handler, callback) {
public void doWork() throws RemoteException {
- mService.addAcount(mResponse, accountType, authTokenType,
+ mService.addAccount(mResponse, accountType, authTokenType,
requiredFeatures, activity != null, optionsIn);
}
}.start();
}
/**
+ * Adds a shared account from the primary user to a secondary user. Adding the shared account
+ * doesn't take effect immediately. When the target user starts up, any pending shared accounts
+ * are attempted to be copied to the target user from the primary via calls to the
+ * authenticator.
+ * @param account the account to share
+ * @param user the target user
+ * @return
+ * @hide
+ */
+ public boolean addSharedAccount(final Account account, UserHandle user) {
+ try {
+ boolean val = mService.addSharedAccountAsUser(account, user.getIdentifier());
+ return val;
+ } catch (RemoteException re) {
+ // won't ever happen
+ throw new RuntimeException(re);
+ }
+ }
+
+ /**
+ * @hide
+ * Removes the shared account.
+ * @param account the account to remove
+ * @param user the user to remove the account from
+ * @return
+ */
+ public boolean removeSharedAccount(final Account account, UserHandle user) {
+ try {
+ boolean val = mService.removeSharedAccountAsUser(account, user.getIdentifier());
+ return val;
+ } catch (RemoteException re) {
+ // won't ever happen
+ throw new RuntimeException(re);
+ }
+ }
+
+ /**
+ * @hide
+ * @param user
+ * @return
+ */
+ public Account[] getSharedAccounts(UserHandle user) {
+ try {
+ return mService.getSharedAccountsAsUser(user.getIdentifier());
+ } catch (RemoteException re) {
+ // won't ever happen
+ throw new RuntimeException(re);
+ }
+ }
+
+ /**
* Confirms that the user knows the password for an account to make extra
* sure they are the owner of the account. The user-entered password can
* be supplied directly, otherwise the authenticator for this account type
@@ -1472,7 +1564,7 @@ public class AccountManager {
}
public void onError(int code, String message) {
- if (code == ERROR_CODE_CANCELED) {
+ if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED) {
// the authenticator indicated that this request was canceled, do so now
cancel(true /* mayInterruptIfRunning */);
return;
@@ -1726,8 +1818,11 @@ public class AccountManager {
};
// have many accounts, launch the chooser
Intent intent = new Intent();
- intent.setClassName("android",
- "android.accounts.ChooseAccountActivity");
+ ComponentName componentName = ComponentName.unflattenFromString(
+ Resources.getSystem().getString(
+ R.string.config_chooseAccountActivity));
+ intent.setClassName(componentName.getPackageName(),
+ componentName.getClassName());
intent.putExtra(KEY_ACCOUNTS, accounts);
intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE,
new AccountManagerResponse(chooseResponse));
@@ -1846,7 +1941,7 @@ public class AccountManager {
* Returns an intent to an {@link Activity} that prompts the user to choose from a list of
* accounts.
* The caller will then typically start the activity by calling
- * <code>startActivityWithResult(intent, ...);</code>.
+ * <code>startActivityForResult(intent, ...);</code>.
* <p>
* On success the activity returns a Bundle with the account name and type specified using
* keys {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE}.
@@ -1883,7 +1978,10 @@ public class AccountManager {
String[] addAccountRequiredFeatures,
Bundle addAccountOptions) {
Intent intent = new Intent();
- intent.setClassName("android", "android.accounts.ChooseTypeAndAccountActivity");
+ ComponentName componentName = ComponentName.unflattenFromString(
+ Resources.getSystem().getString(R.string.config_chooseTypeAndAccountActivity));
+ intent.setClassName(componentName.getPackageName(),
+ componentName.getClassName());
intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST,
allowableAccounts);
intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY,
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
deleted file mode 100644
index 0490d8e..0000000
--- a/core/java/android/accounts/AccountManagerService.java
+++ /dev/null
@@ -1,2571 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accounts;
-
-import android.Manifest;
-import android.app.ActivityManager;
-import android.app.ActivityManagerNative;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-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.RegisteredServicesCache;
-import android.content.pm.RegisteredServicesCacheListener;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.R;
-import com.android.internal.util.IndentingPrintWriter;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Sets;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * A system service that provides account, password, and authtoken management for all
- * accounts on the device. Some of these calls are implemented with the help of the corresponding
- * {@link IAccountAuthenticator} services. This service is not accessed by users directly,
- * instead one uses an instance of {@link AccountManager}, which can be accessed as follows:
- * AccountManager accountManager = AccountManager.get(context);
- * @hide
- */
-public class AccountManagerService
- extends IAccountManager.Stub
- implements RegisteredServicesCacheListener<AuthenticatorDescription> {
- private static final String TAG = "AccountManagerService";
-
- private static final int TIMEOUT_DELAY_MS = 1000 * 60;
- private static final String DATABASE_NAME = "accounts.db";
- private static final int DATABASE_VERSION = 4;
-
- private final Context mContext;
-
- private final PackageManager mPackageManager;
- private UserManager mUserManager;
-
- private HandlerThread mMessageThread;
- private final MessageHandler mMessageHandler;
-
- // Messages that can be sent on mHandler
- private static final int MESSAGE_TIMED_OUT = 3;
-
- private final IAccountAuthenticatorCache mAuthenticatorCache;
-
- private static final String TABLE_ACCOUNTS = "accounts";
- private static final String ACCOUNTS_ID = "_id";
- private static final String ACCOUNTS_NAME = "name";
- private static final String ACCOUNTS_TYPE = "type";
- private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
- private static final String ACCOUNTS_PASSWORD = "password";
-
- private static final String TABLE_AUTHTOKENS = "authtokens";
- private static final String AUTHTOKENS_ID = "_id";
- private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id";
- private static final String AUTHTOKENS_TYPE = "type";
- private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
-
- private static final String TABLE_GRANTS = "grants";
- private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
- private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
- private static final String GRANTS_GRANTEE_UID = "uid";
-
- private static final String TABLE_EXTRAS = "extras";
- private static final String EXTRAS_ID = "_id";
- private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
- private static final String EXTRAS_KEY = "key";
- private static final String EXTRAS_VALUE = "value";
-
- private static final String TABLE_META = "meta";
- private static final String META_KEY = "key";
- private static final String META_VALUE = "value";
-
- private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
- new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
- private static final Intent ACCOUNTS_CHANGED_INTENT;
-
- private static final String COUNT_OF_MATCHING_GRANTS = ""
- + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
- + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID
- + " AND " + GRANTS_GRANTEE_UID + "=?"
- + " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?"
- + " AND " + ACCOUNTS_NAME + "=?"
- + " AND " + ACCOUNTS_TYPE + "=?";
-
- private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT =
- AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
- private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE,
- AUTHTOKENS_AUTHTOKEN};
-
- private static final String SELECTION_USERDATA_BY_ACCOUNT =
- EXTRAS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
- private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE};
-
- private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
- private final AtomicInteger mNotificationIds = new AtomicInteger(1);
-
- static class UserAccounts {
- private final int userId;
- private final DatabaseHelper openHelper;
- private final HashMap<Pair<Pair<Account, String>, Integer>, Integer>
- credentialsPermissionNotificationIds =
- new HashMap<Pair<Pair<Account, String>, Integer>, Integer>();
- private final HashMap<Account, Integer> signinRequiredNotificationIds =
- new HashMap<Account, Integer>();
- private final Object cacheLock = new Object();
- /** protected by the {@link #cacheLock} */
- private final HashMap<String, Account[]> accountCache =
- new LinkedHashMap<String, Account[]>();
- /** protected by the {@link #cacheLock} */
- private HashMap<Account, HashMap<String, String>> userDataCache =
- new HashMap<Account, HashMap<String, String>>();
- /** protected by the {@link #cacheLock} */
- private HashMap<Account, HashMap<String, String>> authTokenCache =
- new HashMap<Account, HashMap<String, String>>();
-
- UserAccounts(Context context, int userId) {
- this.userId = userId;
- synchronized (cacheLock) {
- openHelper = new DatabaseHelper(context, userId);
- }
- }
- }
-
- private final SparseArray<UserAccounts> mUsers = new SparseArray<UserAccounts>();
-
- private static AtomicReference<AccountManagerService> sThis =
- new AtomicReference<AccountManagerService>();
- private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
-
- static {
- ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
- ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- }
-
-
- /**
- * This should only be called by system code. One should only call this after the service
- * has started.
- * @return a reference to the AccountManagerService instance
- * @hide
- */
- public static AccountManagerService getSingleton() {
- return sThis.get();
- }
-
- public AccountManagerService(Context context) {
- this(context, context.getPackageManager(), new AccountAuthenticatorCache(context));
- }
-
- public AccountManagerService(Context context, PackageManager packageManager,
- IAccountAuthenticatorCache authenticatorCache) {
- mContext = context;
- mPackageManager = packageManager;
-
- mMessageThread = new HandlerThread("AccountManagerService");
- mMessageThread.start();
- mMessageHandler = new MessageHandler(mMessageThread.getLooper());
-
- mAuthenticatorCache = authenticatorCache;
- mAuthenticatorCache.setListener(this, null /* Handler */);
-
- sThis.set(this);
-
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- intentFilter.addDataScheme("package");
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context1, Intent intent) {
- purgeOldGrantsAll();
- }
- }, intentFilter);
-
- IntentFilter userFilter = new IntentFilter();
- userFilter.addAction(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- onUserRemoved(intent);
- }
- }, userFilter);
- }
-
- public void systemReady() {
- }
-
- 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);
- if (accounts == null) {
- accounts = new UserAccounts(mContext, userId);
- mUsers.append(userId, accounts);
- purgeOldGrants(accounts);
- validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
- }
- return accounts;
- }
- }
-
- private void purgeOldGrantsAll() {
- synchronized (mUsers) {
- for (int i = 0; i < mUsers.size(); i++) {
- purgeOldGrants(mUsers.valueAt(i));
- }
- }
- }
-
- private void purgeOldGrants(UserAccounts accounts) {
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- final Cursor cursor = db.query(TABLE_GRANTS,
- new String[]{GRANTS_GRANTEE_UID},
- null, null, GRANTS_GRANTEE_UID, null, null);
- try {
- while (cursor.moveToNext()) {
- final int uid = cursor.getInt(0);
- final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null;
- if (packageExists) {
- continue;
- }
- Log.d(TAG, "deleting grants for UID " + uid
- + " because its package is no longer installed");
- db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?",
- new String[]{Integer.toString(uid)});
- }
- } finally {
- cursor.close();
- }
- }
- }
-
- /**
- * Validate internal set of accounts against installed authenticators for
- * given user. Clears cached authenticators before validating.
- */
- public void validateAccounts(int userId) {
- final UserAccounts accounts = getUserAccounts(userId);
-
- // Invalidate user-specific cache to make sure we catch any
- // removed authenticators.
- validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
- }
-
- /**
- * Validate internal set of accounts against installed authenticators for
- * given user. Clear cached authenticators before validating when requested.
- */
- private void validateAccountsInternal(
- UserAccounts accounts, boolean invalidateAuthenticatorCache) {
- if (invalidateAuthenticatorCache) {
- mAuthenticatorCache.invalidateCache(accounts.userId);
- }
-
- final HashSet<AuthenticatorDescription> knownAuth = Sets.newHashSet();
- for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> service :
- mAuthenticatorCache.getAllServices(accounts.userId)) {
- knownAuth.add(service.type);
- }
-
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- boolean accountDeleted = false;
- Cursor cursor = db.query(TABLE_ACCOUNTS,
- new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME},
- null, null, null, null, null);
- try {
- accounts.accountCache.clear();
- final HashMap<String, ArrayList<String>> accountNamesByType =
- new LinkedHashMap<String, ArrayList<String>>();
- while (cursor.moveToNext()) {
- final long accountId = cursor.getLong(0);
- final String accountType = cursor.getString(1);
- final String accountName = cursor.getString(2);
-
- if (!knownAuth.contains(AuthenticatorDescription.newKey(accountType))) {
- Slog.w(TAG, "deleting account " + accountName + " because type "
- + accountType + " no longer has a registered authenticator");
- db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null);
- accountDeleted = true;
- final Account account = new Account(accountName, accountType);
- accounts.userDataCache.remove(account);
- accounts.authTokenCache.remove(account);
- } else {
- ArrayList<String> accountNames = accountNamesByType.get(accountType);
- if (accountNames == null) {
- accountNames = new ArrayList<String>();
- accountNamesByType.put(accountType, accountNames);
- }
- accountNames.add(accountName);
- }
- }
- for (Map.Entry<String, ArrayList<String>> cur
- : accountNamesByType.entrySet()) {
- final String accountType = cur.getKey();
- final ArrayList<String> accountNames = cur.getValue();
- final Account[] accountsForType = new Account[accountNames.size()];
- int i = 0;
- for (String accountName : accountNames) {
- accountsForType[i] = new Account(accountName, accountType);
- ++i;
- }
- accounts.accountCache.put(accountType, accountsForType);
- }
- } finally {
- cursor.close();
- if (accountDeleted) {
- sendAccountsChangedBroadcast(accounts.userId);
- }
- }
- }
- }
-
- private UserAccounts getUserAccountsForCaller() {
- return getUserAccounts(UserHandle.getCallingUserId());
- }
-
- protected UserAccounts getUserAccounts(int userId) {
- synchronized (mUsers) {
- UserAccounts accounts = mUsers.get(userId);
- if (accounts == null) {
- accounts = initUser(userId);
- mUsers.append(userId, accounts);
- }
- return accounts;
- }
- }
-
- private void onUserRemoved(Intent intent) {
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (userId < 1) return;
-
- UserAccounts accounts;
- synchronized (mUsers) {
- accounts = mUsers.get(userId);
- mUsers.remove(userId);
- }
- if (accounts == null) {
- File dbFile = new File(getDatabaseName(userId));
- dbFile.delete();
- return;
- }
-
- synchronized (accounts.cacheLock) {
- accounts.openHelper.close();
- File dbFile = new File(getDatabaseName(userId));
- dbFile.delete();
- }
- }
-
- @Override
- public void onServiceChanged(AuthenticatorDescription desc, int userId, boolean removed) {
- Slog.d(TAG, "onServiceChanged() for userId " + userId);
- validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */);
- }
-
- public String getPassword(Account account) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getPassword: " + account
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- checkAuthenticateAccountsPermission(account);
-
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- return readPasswordInternal(accounts, account);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private String readPasswordInternal(UserAccounts accounts, Account account) {
- if (account == null) {
- return null;
- }
-
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD},
- ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
- new String[]{account.name, account.type}, null, null, null);
- try {
- if (cursor.moveToNext()) {
- return cursor.getString(0);
- }
- return null;
- } finally {
- cursor.close();
- }
- }
- }
-
- public String getUserData(Account account, String key) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getUserData: " + account
- + ", key " + key
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- if (key == null) throw new IllegalArgumentException("key is null");
- checkAuthenticateAccountsPermission(account);
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- return readUserDataInternal(accounts, account, key);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public AuthenticatorDescription[] getAuthenticatorTypes() {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getAuthenticatorTypes: "
- + "caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- final int userId = UserHandle.getCallingUserId();
- final long identityToken = clearCallingIdentity();
- try {
- Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
- authenticatorCollection = mAuthenticatorCache.getAllServices(userId);
- AuthenticatorDescription[] types =
- new AuthenticatorDescription[authenticatorCollection.size()];
- int i = 0;
- for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator
- : authenticatorCollection) {
- types[i] = authenticator.type;
- i++;
- }
- return types;
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public boolean addAccount(Account account, String password, Bundle extras) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "addAccount: " + account
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- checkAuthenticateAccountsPermission(account);
-
- UserAccounts accounts = getUserAccountsForCaller();
- // fails if the account already exists
- long identityToken = clearCallingIdentity();
- try {
- return addAccountInternal(accounts, account, password, extras);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private boolean addAccountInternal(UserAccounts accounts, Account account, String password,
- Bundle extras) {
- if (account == null) {
- return false;
- }
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- long numMatches = DatabaseUtils.longForQuery(db,
- "select count(*) from " + TABLE_ACCOUNTS
- + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
- new String[]{account.name, account.type});
- if (numMatches > 0) {
- Log.w(TAG, "insertAccountIntoDatabase: " + account
- + ", skipping since the account already exists");
- return false;
- }
- ContentValues values = new ContentValues();
- values.put(ACCOUNTS_NAME, account.name);
- values.put(ACCOUNTS_TYPE, account.type);
- values.put(ACCOUNTS_PASSWORD, password);
- long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
- if (accountId < 0) {
- Log.w(TAG, "insertAccountIntoDatabase: " + account
- + ", skipping the DB insert failed");
- return false;
- }
- if (extras != null) {
- for (String key : extras.keySet()) {
- final String value = extras.getString(key);
- if (insertExtraLocked(db, accountId, key, value) < 0) {
- Log.w(TAG, "insertAccountIntoDatabase: " + account
- + ", skipping since insertExtra failed for key " + key);
- return false;
- }
- }
- }
- db.setTransactionSuccessful();
- insertAccountIntoCacheLocked(accounts, account);
- } finally {
- db.endTransaction();
- }
- sendAccountsChangedBroadcast(accounts.userId);
- return true;
- }
- }
-
- private long insertExtraLocked(SQLiteDatabase db, long accountId, String key, String value) {
- ContentValues values = new ContentValues();
- values.put(EXTRAS_KEY, key);
- values.put(EXTRAS_ACCOUNTS_ID, accountId);
- values.put(EXTRAS_VALUE, value);
- return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
- }
-
- public void hasFeatures(IAccountManagerResponse response,
- Account account, String[] features) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "hasFeatures: " + account
- + ", response " + response
- + ", features " + stringArrayToString(features)
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- if (features == null) throw new IllegalArgumentException("features is null");
- checkReadAccountsPermission();
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- new TestFeaturesSession(accounts, response, account, features).bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private class TestFeaturesSession extends Session {
- private final String[] mFeatures;
- private final Account mAccount;
-
- public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response,
- Account account, String[] features) {
- super(accounts, response, account.type, false /* expectActivityLaunch */,
- true /* stripAuthTokenFromResult */);
- mFeatures = features;
- mAccount = account;
- }
-
- public void run() throws RemoteException {
- try {
- mAuthenticator.hasFeatures(this, mAccount, mFeatures);
- } catch (RemoteException e) {
- onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
- }
- }
-
- public void onResult(Bundle result) {
- IAccountManagerResponse response = getResponseAndClose();
- if (response != null) {
- try {
- if (result == null) {
- response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
- return;
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
- + response);
- }
- final Bundle newResult = new Bundle();
- newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT,
- result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false));
- response.onResult(newResult);
- } catch (RemoteException e) {
- // if the caller is dead then there is no one to care about remote exceptions
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "failure while notifying response", e);
- }
- }
- }
- }
-
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", hasFeatures"
- + ", " + mAccount
- + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
- }
- }
-
- public void removeAccount(IAccountManagerResponse response, Account account) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "removeAccount: " + account
- + ", response " + response
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- 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), 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, user);
- }
- }
- }
-
- try {
- new RemoveAccountSession(accounts, response, account).bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private class RemoveAccountSession extends Session {
- final Account mAccount;
- public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response,
- Account account) {
- super(accounts, response, account.type, false /* expectActivityLaunch */,
- true /* stripAuthTokenFromResult */);
- mAccount = account;
- }
-
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", removeAccount"
- + ", account " + mAccount;
- }
-
- public void run() throws RemoteException {
- mAuthenticator.getAccountRemovalAllowed(this, mAccount);
- }
-
- public void onResult(Bundle result) {
- if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
- && !result.containsKey(AccountManager.KEY_INTENT)) {
- final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
- if (removalAllowed) {
- removeAccountInternal(mAccounts, mAccount);
- }
- IAccountManagerResponse response = getResponseAndClose();
- if (response != null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
- + response);
- }
- Bundle result2 = new Bundle();
- result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
- try {
- response.onResult(result2);
- } catch (RemoteException e) {
- // ignore
- }
- }
- }
- super.onResult(result);
- }
- }
-
- /* For testing */
- protected void removeAccountInternal(Account account) {
- removeAccountInternal(getUserAccountsForCaller(), account);
- }
-
- private void removeAccountInternal(UserAccounts accounts, Account account) {
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
- new String[]{account.name, account.type});
- removeAccountFromCacheLocked(accounts, account);
- sendAccountsChangedBroadcast(accounts.userId);
- }
- }
-
- public void invalidateAuthToken(String accountType, String authToken) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "invalidateAuthToken: accountType " + accountType
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- if (authToken == null) throw new IllegalArgumentException("authToken is null");
- checkManageAccountsOrUseCredentialsPermissions();
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- invalidateAuthTokenLocked(accounts, db, accountType, authToken);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db,
- String accountType, String authToken) {
- if (authToken == null || accountType == null) {
- return;
- }
- Cursor cursor = db.rawQuery(
- "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
- + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
- + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
- + " FROM " + TABLE_ACCOUNTS
- + " JOIN " + TABLE_AUTHTOKENS
- + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
- + " = " + AUTHTOKENS_ACCOUNTS_ID
- + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND "
- + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
- new String[]{authToken, accountType});
- try {
- while (cursor.moveToNext()) {
- long authTokenId = cursor.getLong(0);
- String accountName = cursor.getString(1);
- String authTokenType = cursor.getString(2);
- db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
- writeAuthTokenIntoCacheLocked(accounts, db, new Account(accountName, accountType),
- authTokenType, null);
- }
- } finally {
- cursor.close();
- }
- }
-
- private boolean saveAuthTokenToDatabase(UserAccounts accounts, Account account, String type,
- String authToken) {
- if (account == null || type == null) {
- return false;
- }
- cancelNotification(getSigninRequiredNotificationId(accounts, account),
- new UserHandle(accounts.userId));
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- long accountId = getAccountIdLocked(db, account);
- if (accountId < 0) {
- return false;
- }
- db.delete(TABLE_AUTHTOKENS,
- AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
- new String[]{type});
- ContentValues values = new ContentValues();
- values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
- values.put(AUTHTOKENS_TYPE, type);
- values.put(AUTHTOKENS_AUTHTOKEN, authToken);
- if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) {
- db.setTransactionSuccessful();
- writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken);
- return true;
- }
- return false;
- } finally {
- db.endTransaction();
- }
- }
- }
-
- public String peekAuthToken(Account account, String authTokenType) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "peekAuthToken: " + account
- + ", authTokenType " + authTokenType
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
- checkAuthenticateAccountsPermission(account);
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- return readAuthTokenInternal(accounts, account, authTokenType);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void setAuthToken(Account account, String authTokenType, String authToken) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "setAuthToken: " + account
- + ", authTokenType " + authTokenType
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
- checkAuthenticateAccountsPermission(account);
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- saveAuthTokenToDatabase(accounts, account, authTokenType, authToken);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void setPassword(Account account, String password) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "setAuthToken: " + account
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- checkAuthenticateAccountsPermission(account);
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- setPasswordInternal(accounts, account, password);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private void setPasswordInternal(UserAccounts accounts, Account account, String password) {
- if (account == null) {
- return;
- }
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- final ContentValues values = new ContentValues();
- values.put(ACCOUNTS_PASSWORD, password);
- final long accountId = getAccountIdLocked(db, account);
- if (accountId >= 0) {
- final String[] argsAccountId = {String.valueOf(accountId)};
- db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
- db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId);
- accounts.authTokenCache.remove(account);
- db.setTransactionSuccessful();
- }
- } finally {
- db.endTransaction();
- }
- sendAccountsChangedBroadcast(accounts.userId);
- }
- }
-
- private void sendAccountsChangedBroadcast(int userId) {
- Log.i(TAG, "the accounts changed, sending broadcast of "
- + ACCOUNTS_CHANGED_INTENT.getAction());
- mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
- }
-
- public void clearPassword(Account account) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "clearPassword: " + account
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- checkManageAccountsPermission();
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- setPasswordInternal(accounts, account, null);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void setUserData(Account account, String key, String value) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "setUserData: " + account
- + ", key " + key
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (key == null) throw new IllegalArgumentException("key is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- checkAuthenticateAccountsPermission(account);
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- setUserdataInternal(accounts, account, key, value);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private void setUserdataInternal(UserAccounts accounts, Account account, String key,
- String value) {
- if (account == null || key == null) {
- return;
- }
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- long accountId = getAccountIdLocked(db, account);
- if (accountId < 0) {
- return;
- }
- long extrasId = getExtrasIdLocked(db, accountId, key);
- if (extrasId < 0 ) {
- extrasId = insertExtraLocked(db, accountId, key, value);
- if (extrasId < 0) {
- return;
- }
- } else {
- ContentValues values = new ContentValues();
- values.put(EXTRAS_VALUE, value);
- if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
- return;
- }
-
- }
- writeUserDataIntoCacheLocked(accounts, db, account, key, value);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
- }
-
- private void onResult(IAccountManagerResponse response, Bundle result) {
- if (result == null) {
- Log.e(TAG, "the result is unexpectedly null", new Exception());
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
- + response);
- }
- try {
- response.onResult(result);
- } catch (RemoteException e) {
- // if the caller is dead then there is no one to care about remote
- // exceptions
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "failure while notifying response", e);
- }
- }
- }
-
- public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType,
- final String authTokenType)
- throws RemoteException {
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
-
- final int callingUid = getCallingUid();
- clearCallingIdentity();
- if (callingUid != android.os.Process.SYSTEM_UID) {
- throw new SecurityException("can only call from system");
- }
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
- long identityToken = clearCallingIdentity();
- try {
- new Session(accounts, response, accountType, false,
- false /* stripAuthTokenFromResult */) {
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", getAuthTokenLabel"
- + ", " + accountType
- + ", authTokenType " + authTokenType;
- }
-
- public void run() throws RemoteException {
- mAuthenticator.getAuthTokenLabel(this, authTokenType);
- }
-
- public void onResult(Bundle result) {
- if (result != null) {
- String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL);
- Bundle bundle = new Bundle();
- bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, label);
- super.onResult(bundle);
- return;
- } else {
- super.onResult(result);
- }
- }
- }.bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void getAuthToken(IAccountManagerResponse response, final Account account,
- final String authTokenType, final boolean notifyOnAuthFailure,
- final boolean expectActivityLaunch, Bundle loginOptionsIn) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getAuthToken: " + account
- + ", response " + response
- + ", authTokenType " + authTokenType
- + ", notifyOnAuthFailure " + notifyOnAuthFailure
- + ", expectActivityLaunch " + expectActivityLaunch
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
- checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
- final UserAccounts accounts = getUserAccountsForCaller();
- final RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
- authenticatorInfo = mAuthenticatorCache.getServiceInfo(
- AuthenticatorDescription.newKey(account.type), accounts.userId);
- final boolean customTokens =
- authenticatorInfo != null && authenticatorInfo.type.customTokens;
-
- // skip the check if customTokens
- final int callerUid = Binder.getCallingUid();
- final boolean permissionGranted = customTokens ||
- permissionIsGranted(account, authTokenType, callerUid);
-
- final Bundle loginOptions = (loginOptionsIn == null) ? new Bundle() :
- loginOptionsIn;
- // let authenticator know the identity of the caller
- loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid);
- loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid());
- if (notifyOnAuthFailure) {
- loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true);
- }
-
- long identityToken = clearCallingIdentity();
- try {
- // if the caller has permission, do the peek. otherwise go the more expensive
- // route of starting a Session
- if (!customTokens && permissionGranted) {
- String authToken = readAuthTokenInternal(accounts, account, authTokenType);
- if (authToken != null) {
- Bundle result = new Bundle();
- result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
- result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
- result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
- onResult(response, result);
- return;
- }
- }
-
- new Session(accounts, response, account.type, expectActivityLaunch,
- false /* stripAuthTokenFromResult */) {
- protected String toDebugString(long now) {
- if (loginOptions != null) loginOptions.keySet();
- return super.toDebugString(now) + ", getAuthToken"
- + ", " + account
- + ", authTokenType " + authTokenType
- + ", loginOptions " + loginOptions
- + ", notifyOnAuthFailure " + notifyOnAuthFailure;
- }
-
- public void run() throws RemoteException {
- // If the caller doesn't have permission then create and return the
- // "grant permission" intent instead of the "getAuthToken" intent.
- if (!permissionGranted) {
- mAuthenticator.getAuthTokenLabel(this, authTokenType);
- } else {
- mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
- }
- }
-
- public void onResult(Bundle result) {
- if (result != null) {
- if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) {
- Intent intent = newGrantCredentialsPermissionIntent(account, callerUid,
- new AccountAuthenticatorResponse(this),
- authTokenType,
- result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL));
- Bundle bundle = new Bundle();
- bundle.putParcelable(AccountManager.KEY_INTENT, intent);
- onResult(bundle);
- return;
- }
- String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
- if (authToken != null) {
- String name = result.getString(AccountManager.KEY_ACCOUNT_NAME);
- String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
- if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) {
- onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
- "the type and name should not be empty");
- return;
- }
- if (!customTokens) {
- saveAuthTokenToDatabase(mAccounts, new Account(name, type),
- authTokenType, authToken);
- }
- }
-
- Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
- if (intent != null && notifyOnAuthFailure && !customTokens) {
- doNotification(mAccounts,
- account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE),
- intent, accounts.userId);
- }
- }
- super.onResult(result);
- }
- }.bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private void createNoCredentialsPermissionNotification(Account account, Intent intent,
- int userId) {
- int uid = intent.getIntExtra(
- GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1);
- String authTokenType = intent.getStringExtra(
- GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE);
- String authTokenLabel = intent.getStringExtra(
- GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL);
-
- Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
- 0 /* when */);
- final String titleAndSubtitle =
- mContext.getString(R.string.permission_request_notification_with_subtitle,
- account.name);
- final int index = titleAndSubtitle.indexOf('\n');
- String title = titleAndSubtitle;
- String subtitle = "";
- if (index > 0) {
- title = titleAndSubtitle.substring(0, index);
- subtitle = titleAndSubtitle.substring(index + 1);
- }
- 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);
- }
-
- private Intent newGrantCredentialsPermissionIntent(Account account, int uid,
- AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) {
-
- Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class);
- // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag.
- // Since it was set in Eclair+ we can't change it without breaking apps using
- // the intent from a non-Activity context.
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addCategory(
- String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid)));
-
- intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account);
- intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType);
- intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response);
- intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, uid);
-
- return intent;
- }
-
- private Integer getCredentialPermissionNotificationId(Account account, String authTokenType,
- int uid) {
- Integer id;
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
- synchronized (accounts.credentialsPermissionNotificationIds) {
- final Pair<Pair<Account, String>, Integer> key =
- new Pair<Pair<Account, String>, Integer>(
- new Pair<Account, String>(account, authTokenType), uid);
- id = accounts.credentialsPermissionNotificationIds.get(key);
- if (id == null) {
- id = mNotificationIds.incrementAndGet();
- accounts.credentialsPermissionNotificationIds.put(key, id);
- }
- }
- return id;
- }
-
- private Integer getSigninRequiredNotificationId(UserAccounts accounts, Account account) {
- Integer id;
- synchronized (accounts.signinRequiredNotificationIds) {
- id = accounts.signinRequiredNotificationIds.get(account);
- if (id == null) {
- id = mNotificationIds.incrementAndGet();
- accounts.signinRequiredNotificationIds.put(account, id);
- }
- }
- return id;
- }
-
- public void addAcount(final IAccountManagerResponse response, final String accountType,
- final String authTokenType, final String[] requiredFeatures,
- final boolean expectActivityLaunch, final Bundle optionsIn) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "addAccount: accountType " + accountType
- + ", response " + response
- + ", authTokenType " + authTokenType
- + ", requiredFeatures " + stringArrayToString(requiredFeatures)
- + ", expectActivityLaunch " + expectActivityLaunch
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- checkManageAccountsPermission();
-
- UserAccounts accounts = getUserAccountsForCaller();
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
- options.putInt(AccountManager.KEY_CALLER_UID, uid);
- options.putInt(AccountManager.KEY_CALLER_PID, pid);
-
- long identityToken = clearCallingIdentity();
- try {
- new Session(accounts, response, accountType, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
- public void run() throws RemoteException {
- mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
- options);
- }
-
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", addAccount"
- + ", accountType " + accountType
- + ", requiredFeatures "
- + (requiredFeatures != null
- ? TextUtils.join(",", requiredFeatures)
- : null);
- }
- }.bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- @Override
- public void confirmCredentialsAsUser(IAccountManagerResponse response,
- final Account account, final Bundle options, final boolean expectActivityLaunch,
- int userId) {
- // Only allow the system process to read accounts of other users
- if (userId != UserHandle.getCallingUserId()
- && Binder.getCallingUid() != android.os.Process.myUid()) {
- throw new SecurityException("User " + UserHandle.getCallingUserId()
- + " trying to confirm account credentials for " + userId);
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "confirmCredentials: " + account
- + ", response " + response
- + ", expectActivityLaunch " + expectActivityLaunch
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- checkManageAccountsPermission();
- UserAccounts accounts = getUserAccounts(userId);
- long identityToken = clearCallingIdentity();
- try {
- new Session(accounts, response, account.type, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
- public void run() throws RemoteException {
- mAuthenticator.confirmCredentials(this, account, options);
- }
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", confirmCredentials"
- + ", " + account;
- }
- }.bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void updateCredentials(IAccountManagerResponse response, final Account account,
- final String authTokenType, final boolean expectActivityLaunch,
- final Bundle loginOptions) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "updateCredentials: " + account
- + ", response " + response
- + ", authTokenType " + authTokenType
- + ", expectActivityLaunch " + expectActivityLaunch
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
- checkManageAccountsPermission();
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- new Session(accounts, response, account.type, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
- public void run() throws RemoteException {
- mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
- }
- protected String toDebugString(long now) {
- if (loginOptions != null) loginOptions.keySet();
- return super.toDebugString(now) + ", updateCredentials"
- + ", " + account
- + ", authTokenType " + authTokenType
- + ", loginOptions " + loginOptions;
- }
- }.bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void editProperties(IAccountManagerResponse response, final String accountType,
- final boolean expectActivityLaunch) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "editProperties: accountType " + accountType
- + ", response " + response
- + ", expectActivityLaunch " + expectActivityLaunch
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- checkManageAccountsPermission();
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- new Session(accounts, response, accountType, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
- public void run() throws RemoteException {
- mAuthenticator.editProperties(this, mAccountType);
- }
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", editProperties"
- + ", accountType " + accountType;
- }
- }.bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private class GetAccountsByTypeAndFeatureSession extends Session {
- private final String[] mFeatures;
- private volatile Account[] mAccountsOfType = null;
- private volatile ArrayList<Account> mAccountsWithFeatures = null;
- private volatile int mCurrentAccount = 0;
-
- public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
- IAccountManagerResponse response, String type, String[] features) {
- super(accounts, response, type, false /* expectActivityLaunch */,
- true /* stripAuthTokenFromResult */);
- mFeatures = features;
- }
-
- public void run() throws RemoteException {
- synchronized (mAccounts.cacheLock) {
- mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType);
- }
- // check whether each account matches the requested features
- mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
- mCurrentAccount = 0;
-
- checkAccount();
- }
-
- public void checkAccount() {
- if (mCurrentAccount >= mAccountsOfType.length) {
- sendResult();
- return;
- }
-
- final IAccountAuthenticator accountAuthenticator = mAuthenticator;
- if (accountAuthenticator == null) {
- // It is possible that the authenticator has died, which is indicated by
- // mAuthenticator being set to null. If this happens then just abort.
- // There is no need to send back a result or error in this case since
- // that already happened when mAuthenticator was cleared.
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "checkAccount: aborting session since we are no longer"
- + " connected to the authenticator, " + toDebugString());
- }
- return;
- }
- try {
- accountAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
- } catch (RemoteException e) {
- onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
- }
- }
-
- public void onResult(Bundle result) {
- mNumResults++;
- if (result == null) {
- onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
- return;
- }
- if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
- mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]);
- }
- mCurrentAccount++;
- checkAccount();
- }
-
- public void sendResult() {
- IAccountManagerResponse response = getResponseAndClose();
- if (response != null) {
- try {
- Account[] accounts = new Account[mAccountsWithFeatures.size()];
- for (int i = 0; i < accounts.length; i++) {
- accounts[i] = mAccountsWithFeatures.get(i);
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
- + response);
- }
- Bundle result = new Bundle();
- result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
- response.onResult(result);
- } catch (RemoteException e) {
- // if the caller is dead then there is no one to care about remote exceptions
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "failure while notifying response", e);
- }
- }
- }
- }
-
-
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
- + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
- }
- }
-
- /**
- * Returns the accounts for a specific user
- * @hide
- */
- public Account[] getAccounts(int userId) {
- checkReadAccountsPermission();
- UserAccounts accounts = getUserAccounts(userId);
- long identityToken = clearCallingIdentity();
- try {
- synchronized (accounts.cacheLock) {
- return getAccountsFromCacheLocked(accounts, null);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- /**
- * Returns accounts for all running users.
- *
- * @hide
- */
- public AccountAndUser[] getRunningAccounts() {
- final int[] runningUserIds;
- try {
- runningUserIds = ActivityManagerNative.getDefault().getRunningUserIds();
- } catch (RemoteException e) {
- // Running in system_server; should never happen
- throw new RuntimeException(e);
- }
- return getAccounts(runningUserIds);
- }
-
- /** {@hide} */
- public AccountAndUser[] getAllAccounts() {
- final List<UserInfo> users = getUserManager().getUsers();
- final int[] userIds = new int[users.size()];
- for (int i = 0; i < userIds.length; i++) {
- userIds[i] = users.get(i).id;
- }
- return getAccounts(userIds);
- }
-
- private AccountAndUser[] getAccounts(int[] userIds) {
- final ArrayList<AccountAndUser> runningAccounts = Lists.newArrayList();
- synchronized (mUsers) {
- for (int userId : userIds) {
- UserAccounts userAccounts = getUserAccounts(userId);
- if (userAccounts == null) continue;
- synchronized (userAccounts.cacheLock) {
- Account[] accounts = getAccountsFromCacheLocked(userAccounts, null);
- for (int a = 0; a < accounts.length; a++) {
- runningAccounts.add(new AccountAndUser(accounts[a], userId));
- }
- }
- }
- }
-
- AccountAndUser[] accountsArray = new AccountAndUser[runningAccounts.size()];
- return runningAccounts.toArray(accountsArray);
- }
-
- @Override
- public Account[] getAccountsAsUser(String type, int userId) {
- // Only allow the system process to read accounts of other users
- if (userId != UserHandle.getCallingUserId()
- && Binder.getCallingUid() != android.os.Process.myUid()) {
- throw new SecurityException("User " + UserHandle.getCallingUserId()
- + " trying to get account for " + userId);
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getAccounts: accountType " + type
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- checkReadAccountsPermission();
- UserAccounts accounts = getUserAccounts(userId);
- long identityToken = clearCallingIdentity();
- try {
- synchronized (accounts.cacheLock) {
- return getAccountsFromCacheLocked(accounts, type);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- @Override
- public Account[] getAccounts(String type) {
- return getAccountsAsUser(type, UserHandle.getCallingUserId());
- }
-
- public void getAccountsByFeatures(IAccountManagerResponse response,
- String type, String[] features) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getAccounts: accountType " + type
- + ", response " + response
- + ", features " + stringArrayToString(features)
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (type == null) throw new IllegalArgumentException("accountType is null");
- checkReadAccountsPermission();
- UserAccounts userAccounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- if (features == null || features.length == 0) {
- Account[] accounts;
- synchronized (userAccounts.cacheLock) {
- accounts = getAccountsFromCacheLocked(userAccounts, type);
- }
- Bundle result = new Bundle();
- result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
- onResult(response, result);
- return;
- }
- new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features).bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private long getAccountIdLocked(SQLiteDatabase db, Account account) {
- Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID},
- "name=? AND type=?", new String[]{account.name, account.type}, null, null, null);
- try {
- if (cursor.moveToNext()) {
- return cursor.getLong(0);
- }
- return -1;
- } finally {
- cursor.close();
- }
- }
-
- private long getExtrasIdLocked(SQLiteDatabase db, long accountId, String key) {
- Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID},
- EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
- new String[]{key}, null, null, null);
- try {
- if (cursor.moveToNext()) {
- return cursor.getLong(0);
- }
- return -1;
- } finally {
- cursor.close();
- }
- }
-
- private abstract class Session extends IAccountAuthenticatorResponse.Stub
- implements IBinder.DeathRecipient, ServiceConnection {
- IAccountManagerResponse mResponse;
- final String mAccountType;
- final boolean mExpectActivityLaunch;
- final long mCreationTime;
-
- public int mNumResults = 0;
- private int mNumRequestContinued = 0;
- private int mNumErrors = 0;
-
-
- IAccountAuthenticator mAuthenticator = null;
-
- private final boolean mStripAuthTokenFromResult;
- protected final UserAccounts mAccounts;
-
- public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
- boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
- super();
- if (response == null) throw new IllegalArgumentException("response is null");
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- mAccounts = accounts;
- mStripAuthTokenFromResult = stripAuthTokenFromResult;
- mResponse = response;
- mAccountType = accountType;
- mExpectActivityLaunch = expectActivityLaunch;
- mCreationTime = SystemClock.elapsedRealtime();
- synchronized (mSessions) {
- mSessions.put(toString(), this);
- }
- try {
- response.asBinder().linkToDeath(this, 0 /* flags */);
- } catch (RemoteException e) {
- mResponse = null;
- binderDied();
- }
- }
-
- IAccountManagerResponse getResponseAndClose() {
- if (mResponse == null) {
- // this session has already been closed
- return null;
- }
- IAccountManagerResponse response = mResponse;
- close(); // this clears mResponse so we need to save the response before this call
- return response;
- }
-
- private void close() {
- synchronized (mSessions) {
- if (mSessions.remove(toString()) == null) {
- // the session was already closed, so bail out now
- return;
- }
- }
- if (mResponse != null) {
- // stop listening for response deaths
- mResponse.asBinder().unlinkToDeath(this, 0 /* flags */);
-
- // clear this so that we don't accidentally send any further results
- mResponse = null;
- }
- cancelTimeout();
- unbind();
- }
-
- public void binderDied() {
- mResponse = null;
- close();
- }
-
- protected String toDebugString() {
- return toDebugString(SystemClock.elapsedRealtime());
- }
-
- protected String toDebugString(long now) {
- return "Session: expectLaunch " + mExpectActivityLaunch
- + ", connected " + (mAuthenticator != null)
- + ", stats (" + mNumResults + "/" + mNumRequestContinued
- + "/" + mNumErrors + ")"
- + ", lifetime " + ((now - mCreationTime) / 1000.0);
- }
-
- void bind() {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
- }
- if (!bindToAuthenticator(mAccountType)) {
- Log.d(TAG, "bind attempt failed for " + toDebugString());
- onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
- }
- }
-
- private void unbind() {
- if (mAuthenticator != null) {
- mAuthenticator = null;
- mContext.unbindService(this);
- }
- }
-
- public void scheduleTimeout() {
- mMessageHandler.sendMessageDelayed(
- mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
- }
-
- public void cancelTimeout() {
- mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
- try {
- run();
- } catch (RemoteException e) {
- onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
- "remote exception");
- }
- }
-
- public void onServiceDisconnected(ComponentName name) {
- mAuthenticator = null;
- IAccountManagerResponse response = getResponseAndClose();
- if (response != null) {
- try {
- response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
- "disconnected");
- } catch (RemoteException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Session.onServiceDisconnected: "
- + "caught RemoteException while responding", e);
- }
- }
- }
- }
-
- public abstract void run() throws RemoteException;
-
- public void onTimedOut() {
- IAccountManagerResponse response = getResponseAndClose();
- if (response != null) {
- try {
- response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
- "timeout");
- } catch (RemoteException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Session.onTimedOut: caught RemoteException while responding",
- e);
- }
- }
- }
- }
-
- @Override
- public void onResult(Bundle result) {
- mNumResults++;
- Intent intent = null;
- if (result != null
- && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
- /*
- * The Authenticator API allows third party authenticators to
- * supply arbitrary intents to other apps that they can run,
- * this can be very bad when those apps are in the system like
- * the System Settings.
- */
- PackageManager pm = mContext.getPackageManager();
- ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
- int targetUid = resolveInfo.activityInfo.applicationInfo.uid;
- int authenticatorUid = Binder.getCallingUid();
- if (PackageManager.SIGNATURE_MATCH !=
- pm.checkSignatures(authenticatorUid, targetUid)) {
- throw new SecurityException(
- "Activity to be started with KEY_INTENT must " +
- "share Authenticator's signatures");
- }
- }
- if (result != null
- && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
- String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
- 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),
- new UserHandle(mAccounts.userId));
- }
- }
- IAccountManagerResponse response;
- if (mExpectActivityLaunch && result != null
- && result.containsKey(AccountManager.KEY_INTENT)) {
- response = mResponse;
- } else {
- response = getResponseAndClose();
- }
- if (response != null) {
- try {
- if (result == null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName()
- + " calling onError() on response " + response);
- }
- response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
- "null bundle returned");
- } else {
- if (mStripAuthTokenFromResult) {
- result.remove(AccountManager.KEY_AUTHTOKEN);
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName()
- + " calling onResult() on response " + response);
- }
- response.onResult(result);
- }
- } catch (RemoteException e) {
- // if the caller is dead then there is no one to care about remote exceptions
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "failure while notifying response", e);
- }
- }
- }
- }
-
- public void onRequestContinued() {
- mNumRequestContinued++;
- }
-
- public void onError(int errorCode, String errorMessage) {
- mNumErrors++;
- IAccountManagerResponse response = getResponseAndClose();
- if (response != null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName()
- + " calling onError() on response " + response);
- }
- try {
- response.onError(errorCode, errorMessage);
- } catch (RemoteException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Session.onError: caught RemoteException while responding", e);
- }
- }
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Session.onError: already closed");
- }
- }
- }
-
- /**
- * find the component name for the authenticator and initiate a bind
- * if no authenticator or the bind fails then return false, otherwise return true
- */
- private boolean bindToAuthenticator(String authenticatorType) {
- final AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
- authenticatorInfo = mAuthenticatorCache.getServiceInfo(
- AuthenticatorDescription.newKey(authenticatorType), mAccounts.userId);
- if (authenticatorInfo == null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there is no authenticator for " + authenticatorType
- + ", bailing out");
- }
- return false;
- }
-
- Intent intent = new Intent();
- intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
- intent.setComponent(authenticatorInfo.componentName);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
- }
- if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE, mAccounts.userId)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
- }
- return false;
- }
-
-
- return true;
- }
- }
-
- private class MessageHandler extends Handler {
- MessageHandler(Looper looper) {
- super(looper);
- }
-
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_TIMED_OUT:
- Session session = (Session)msg.obj;
- session.onTimedOut();
- break;
-
- default:
- throw new IllegalStateException("unhandled message: " + msg.what);
- }
- }
- }
-
- private static String getDatabaseName(int userId) {
- File systemDir = Environment.getSystemSecureDirectory();
- 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
- // accidentally created in the old location, causing the new one to become corrupted
- // as well.
- 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 = Environment.getUserSystemDirectory(userId);
- if (!userDir.exists()) {
- if (!userDir.mkdirs()) {
- throw new IllegalStateException("User dir cannot be created: " + userDir);
- }
- }
- if (!oldFile.renameTo(databaseFile)) {
- throw new IllegalStateException("User dir cannot be migrated: " + databaseFile);
- }
- }
- }
- return databaseFile.getPath();
- }
-
- static class DatabaseHelper extends SQLiteOpenHelper {
-
- public DatabaseHelper(Context context, int userId) {
- super(context, AccountManagerService.getDatabaseName(userId), null, DATABASE_VERSION);
- }
-
- /**
- * This call needs to be made while the mCacheLock is held. The way to
- * ensure this is to get the lock any time a method is called ont the DatabaseHelper
- * @param db The database.
- */
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
- + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
- + ACCOUNTS_NAME + " TEXT NOT NULL, "
- + ACCOUNTS_TYPE + " TEXT NOT NULL, "
- + ACCOUNTS_PASSWORD + " TEXT, "
- + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
-
- db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
- + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
- + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
- + AUTHTOKENS_TYPE + " TEXT NOT NULL, "
- + AUTHTOKENS_AUTHTOKEN + " TEXT, "
- + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
-
- createGrantsTable(db);
-
- db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
- + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
- + EXTRAS_ACCOUNTS_ID + " INTEGER, "
- + EXTRAS_KEY + " TEXT NOT NULL, "
- + EXTRAS_VALUE + " TEXT, "
- + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
-
- db.execSQL("CREATE TABLE " + TABLE_META + " ( "
- + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
- + META_VALUE + " TEXT)");
-
- createAccountsDeletionTrigger(db);
- }
-
- private void createAccountsDeletionTrigger(SQLiteDatabase db) {
- db.execSQL(""
- + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
- + " BEGIN"
- + " DELETE FROM " + TABLE_AUTHTOKENS
- + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
- + " DELETE FROM " + TABLE_EXTRAS
- + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
- + " DELETE FROM " + TABLE_GRANTS
- + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
- + " END");
- }
-
- private void createGrantsTable(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
- + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
- + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, "
- + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
- + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
- + "," + GRANTS_GRANTEE_UID + "))");
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
-
- if (oldVersion == 1) {
- // no longer need to do anything since the work is done
- // when upgrading from version 2
- oldVersion++;
- }
-
- if (oldVersion == 2) {
- createGrantsTable(db);
- db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete");
- createAccountsDeletionTrigger(db);
- oldVersion++;
- }
-
- if (oldVersion == 3) {
- db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE +
- " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'");
- oldVersion++;
- }
- }
-
- @Override
- public void onOpen(SQLiteDatabase db) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
- }
- }
-
- public IBinder onBind(Intent intent) {
- return asBinder();
- }
-
- /**
- * Searches array of arguments for the specified string
- * @param args array of argument strings
- * @param value value to search for
- * @return true if the value is contained in the array
- */
- private static boolean scanArgs(String[] args, String value) {
- if (args != null) {
- for (String arg : args) {
- if (value.equals(arg)) {
- return true;
- }
- }
- }
- return false;
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
- != PackageManager.PERMISSION_GRANTED) {
- fout.println("Permission Denial: can't dump AccountsManager from from pid="
- + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
- + " without permission " + android.Manifest.permission.DUMP);
- return;
- }
- final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c");
- final IndentingPrintWriter ipw = new IndentingPrintWriter(fout, " ");
-
- final List<UserInfo> users = getUserManager().getUsers();
- for (UserInfo user : users) {
- ipw.println("User " + user + ":");
- ipw.increaseIndent();
- dumpUser(getUserAccounts(user.id), fd, ipw, args, isCheckinRequest);
- ipw.println();
- ipw.decreaseIndent();
- }
- }
-
- private void dumpUser(UserAccounts userAccounts, FileDescriptor fd, PrintWriter fout,
- String[] args, boolean isCheckinRequest) {
- synchronized (userAccounts.cacheLock) {
- final SQLiteDatabase db = userAccounts.openHelper.getReadableDatabase();
-
- if (isCheckinRequest) {
- // This is a checkin request. *Only* upload the account types and the count of each.
- Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION,
- null, null, ACCOUNTS_TYPE, null, null);
- try {
- while (cursor.moveToNext()) {
- // print type,count
- fout.println(cursor.getString(0) + "," + cursor.getString(1));
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- } else {
- Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */);
- fout.println("Accounts: " + accounts.length);
- for (Account account : accounts) {
- fout.println(" " + account);
- }
-
- fout.println();
- synchronized (mSessions) {
- final long now = SystemClock.elapsedRealtime();
- fout.println("Active Sessions: " + mSessions.size());
- for (Session session : mSessions.values()) {
- fout.println(" " + session.toDebugString(now));
- }
- }
-
- fout.println();
- mAuthenticatorCache.dump(fd, fout, args, userAccounts.userId);
- }
- }
- }
-
- private void doNotification(UserAccounts accounts, Account account, CharSequence message,
- Intent intent, int userId) {
- long identityToken = clearCallingIdentity();
- try {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "doNotification: " + message + " intent:" + intent);
- }
-
- if (intent.getComponent() != null &&
- GrantCredentialsPermissionActivity.class.getName().equals(
- intent.getComponent().getClassName())) {
- 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.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,
- UserHandle user) {
- ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
- .notifyAsUser(null, notificationId, n, user);
- }
-
- protected void cancelNotification(int id, UserHandle user) {
- long identityToken = clearCallingIdentity();
- try {
- ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
- .cancelAsUser(null, id, user);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- /** Succeeds if any of the specified permissions are granted. */
- private void checkBinderPermission(String... permissions) {
- final int uid = Binder.getCallingUid();
-
- for (String perm : permissions) {
- if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, " caller uid " + uid + " has " + perm);
- }
- return;
- }
- }
-
- String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions);
- Log.w(TAG, " " + msg);
- throw new SecurityException(msg);
- }
-
- private boolean inSystemImage(int callingUid) {
- final int callingUserId = UserHandle.getUserId(callingUid);
-
- final PackageManager userPackageManager;
- try {
- userPackageManager = mContext.createPackageContextAsUser(
- "android", 0, new UserHandle(callingUserId)).getPackageManager();
- } catch (NameNotFoundException e) {
- return false;
- }
-
- String[] packages = userPackageManager.getPackagesForUid(callingUid);
- for (String name : packages) {
- try {
- PackageInfo packageInfo = userPackageManager.getPackageInfo(name, 0 /* flags */);
- if (packageInfo != null
- && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
- return false;
- }
-
- private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
- final boolean inSystemImage = inSystemImage(callerUid);
- final boolean fromAuthenticator = account != null
- && hasAuthenticatorUid(account.type, callerUid);
- final boolean hasExplicitGrants = account != null
- && hasExplicitlyGrantedPermission(account, authTokenType, callerUid);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid "
- + callerUid + ", " + account
- + ": is authenticator? " + fromAuthenticator
- + ", has explicit permission? " + hasExplicitGrants);
- }
- return fromAuthenticator || hasExplicitGrants || inSystemImage;
- }
-
- private boolean hasAuthenticatorUid(String accountType, int callingUid) {
- final int callingUserId = UserHandle.getUserId(callingUid);
- for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo :
- mAuthenticatorCache.getAllServices(callingUserId)) {
- if (serviceInfo.type.type.equals(accountType)) {
- return (serviceInfo.uid == callingUid) ||
- (mPackageManager.checkSignatures(serviceInfo.uid, callingUid)
- == PackageManager.SIGNATURE_MATCH);
- }
- }
- return false;
- }
-
- private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType,
- int callerUid) {
- if (callerUid == android.os.Process.SYSTEM_UID) {
- return true;
- }
- UserAccounts accounts = getUserAccountsForCaller();
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- String[] args = { String.valueOf(callerUid), authTokenType,
- account.name, account.type};
- final boolean permissionGranted =
- DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0;
- if (!permissionGranted && ActivityManager.isRunningInTestHarness()) {
- // TODO: Skip this check when running automated tests. Replace this
- // with a more general solution.
- Log.d(TAG, "no credentials permission for usage of " + account + ", "
- + authTokenType + " by uid " + callerUid
- + " but ignoring since device is in test harness.");
- return true;
- }
- return permissionGranted;
- }
- }
-
- private void checkCallingUidAgainstAuthenticator(Account account) {
- final int uid = Binder.getCallingUid();
- if (account == null || !hasAuthenticatorUid(account.type, uid)) {
- String msg = "caller uid " + uid + " is different than the authenticator's uid";
- Log.w(TAG, msg);
- throw new SecurityException(msg);
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "caller uid " + uid + " is the same as the authenticator's uid");
- }
- }
-
- private void checkAuthenticateAccountsPermission(Account account) {
- checkBinderPermission(Manifest.permission.AUTHENTICATE_ACCOUNTS);
- checkCallingUidAgainstAuthenticator(account);
- }
-
- private void checkReadAccountsPermission() {
- checkBinderPermission(Manifest.permission.GET_ACCOUNTS);
- }
-
- private void checkManageAccountsPermission() {
- checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS);
- }
-
- private void checkManageAccountsOrUseCredentialsPermissions() {
- checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS,
- Manifest.permission.USE_CREDENTIALS);
- }
-
- public void updateAppPermission(Account account, String authTokenType, int uid, boolean value)
- throws RemoteException {
- final int callingUid = getCallingUid();
-
- if (callingUid != android.os.Process.SYSTEM_UID) {
- throw new SecurityException();
- }
-
- if (value) {
- grantAppPermission(account, authTokenType, uid);
- } else {
- revokeAppPermission(account, authTokenType, uid);
- }
- }
-
- /**
- * Allow callers with the given uid permission to get credentials for account/authTokenType.
- * <p>
- * Although this is public it can only be accessed via the AccountManagerService object
- * which is in the system. This means we don't need to protect it with permissions.
- * @hide
- */
- private void grantAppPermission(Account account, String authTokenType, int uid) {
- if (account == null || authTokenType == null) {
- Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception());
- return;
- }
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- long accountId = getAccountIdLocked(db, account);
- if (accountId >= 0) {
- ContentValues values = new ContentValues();
- values.put(GRANTS_ACCOUNTS_ID, accountId);
- values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
- values.put(GRANTS_GRANTEE_UID, uid);
- db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
- db.setTransactionSuccessful();
- }
- } finally {
- db.endTransaction();
- }
- cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
- new UserHandle(accounts.userId));
- }
- }
-
- /**
- * Don't allow callers with the given uid permission to get credentials for
- * account/authTokenType.
- * <p>
- * Although this is public it can only be accessed via the AccountManagerService object
- * which is in the system. This means we don't need to protect it with permissions.
- * @hide
- */
- private void revokeAppPermission(Account account, String authTokenType, int uid) {
- if (account == null || authTokenType == null) {
- Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception());
- return;
- }
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- long accountId = getAccountIdLocked(db, account);
- if (accountId >= 0) {
- db.delete(TABLE_GRANTS,
- GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
- + GRANTS_GRANTEE_UID + "=?",
- new String[]{String.valueOf(accountId), authTokenType,
- String.valueOf(uid)});
- db.setTransactionSuccessful();
- }
- } finally {
- db.endTransaction();
- }
- cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
- new UserHandle(accounts.userId));
- }
- }
-
- static final private String stringArrayToString(String[] value) {
- return value != null ? ("[" + TextUtils.join(",", value) + "]") : null;
- }
-
- private void removeAccountFromCacheLocked(UserAccounts accounts, Account account) {
- final Account[] oldAccountsForType = accounts.accountCache.get(account.type);
- if (oldAccountsForType != null) {
- ArrayList<Account> newAccountsList = new ArrayList<Account>();
- for (Account curAccount : oldAccountsForType) {
- if (!curAccount.equals(account)) {
- newAccountsList.add(curAccount);
- }
- }
- if (newAccountsList.isEmpty()) {
- accounts.accountCache.remove(account.type);
- } else {
- Account[] newAccountsForType = new Account[newAccountsList.size()];
- newAccountsForType = newAccountsList.toArray(newAccountsForType);
- accounts.accountCache.put(account.type, newAccountsForType);
- }
- }
- accounts.userDataCache.remove(account);
- accounts.authTokenCache.remove(account);
- }
-
- /**
- * This assumes that the caller has already checked that the account is not already present.
- */
- private void insertAccountIntoCacheLocked(UserAccounts accounts, Account account) {
- Account[] accountsForType = accounts.accountCache.get(account.type);
- int oldLength = (accountsForType != null) ? accountsForType.length : 0;
- Account[] newAccountsForType = new Account[oldLength + 1];
- if (accountsForType != null) {
- System.arraycopy(accountsForType, 0, newAccountsForType, 0, oldLength);
- }
- newAccountsForType[oldLength] = account;
- accounts.accountCache.put(account.type, newAccountsForType);
- }
-
- protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType) {
- if (accountType != null) {
- final Account[] accounts = userAccounts.accountCache.get(accountType);
- if (accounts == null) {
- return EMPTY_ACCOUNT_ARRAY;
- } else {
- return Arrays.copyOf(accounts, accounts.length);
- }
- } else {
- int totalLength = 0;
- for (Account[] accounts : userAccounts.accountCache.values()) {
- totalLength += accounts.length;
- }
- if (totalLength == 0) {
- return EMPTY_ACCOUNT_ARRAY;
- }
- Account[] accounts = new Account[totalLength];
- totalLength = 0;
- for (Account[] accountsOfType : userAccounts.accountCache.values()) {
- System.arraycopy(accountsOfType, 0, accounts, totalLength,
- accountsOfType.length);
- totalLength += accountsOfType.length;
- }
- return accounts;
- }
- }
-
- protected void writeUserDataIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
- Account account, String key, String value) {
- HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
- if (userDataForAccount == null) {
- userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
- accounts.userDataCache.put(account, userDataForAccount);
- }
- if (value == null) {
- userDataForAccount.remove(key);
- } else {
- userDataForAccount.put(key, value);
- }
- }
-
- protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
- Account account, String key, String value) {
- HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
- if (authTokensForAccount == null) {
- authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
- accounts.authTokenCache.put(account, authTokensForAccount);
- }
- if (value == null) {
- authTokensForAccount.remove(key);
- } else {
- authTokensForAccount.put(key, value);
- }
- }
-
- protected String readAuthTokenInternal(UserAccounts accounts, Account account,
- String authTokenType) {
- synchronized (accounts.cacheLock) {
- HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
- if (authTokensForAccount == null) {
- // need to populate the cache for this account
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
- accounts.authTokenCache.put(account, authTokensForAccount);
- }
- return authTokensForAccount.get(authTokenType);
- }
- }
-
- protected String readUserDataInternal(UserAccounts accounts, Account account, String key) {
- synchronized (accounts.cacheLock) {
- HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
- if (userDataForAccount == null) {
- // need to populate the cache for this account
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
- accounts.userDataCache.put(account, userDataForAccount);
- }
- return userDataForAccount.get(key);
- }
- }
-
- protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked(
- final SQLiteDatabase db, Account account) {
- HashMap<String, String> userDataForAccount = new HashMap<String, String>();
- Cursor cursor = db.query(TABLE_EXTRAS,
- COLUMNS_EXTRAS_KEY_AND_VALUE,
- SELECTION_USERDATA_BY_ACCOUNT,
- new String[]{account.name, account.type},
- null, null, null);
- try {
- while (cursor.moveToNext()) {
- final String tmpkey = cursor.getString(0);
- final String value = cursor.getString(1);
- userDataForAccount.put(tmpkey, value);
- }
- } finally {
- cursor.close();
- }
- return userDataForAccount;
- }
-
- protected HashMap<String, String> readAuthTokensForAccountFromDatabaseLocked(
- final SQLiteDatabase db, Account account) {
- HashMap<String, String> authTokensForAccount = new HashMap<String, String>();
- Cursor cursor = db.query(TABLE_AUTHTOKENS,
- COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
- SELECTION_AUTHTOKENS_BY_ACCOUNT,
- new String[]{account.name, account.type},
- null, null, null);
- try {
- while (cursor.moveToNext()) {
- final String type = cursor.getString(0);
- final String authToken = cursor.getString(1);
- authTokensForAccount.put(type, authToken);
- }
- } finally {
- cursor.close();
- }
- return authTokensForAccount;
- }
-}
diff --git a/core/java/android/accounts/CantAddAccountActivity.java b/core/java/android/accounts/CantAddAccountActivity.java
new file mode 100644
index 0000000..e1717a6
--- /dev/null
+++ b/core/java/android/accounts/CantAddAccountActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.accounts;
+
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.internal.R;
+
+/**
+ * @hide
+ * Just shows an error message about the account restrictions for the limited user.
+ */
+public class CantAddAccountActivity extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.app_not_authorized);
+ }
+
+ public void onCancelButtonClicked(View view) {
+ onBackPressed();
+ }
+}
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 5358bc7..58eb66f 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -18,9 +18,14 @@ package android.accounts;
import com.google.android.collect.Sets;
import android.app.Activity;
+import android.app.ActivityManagerNative;
import android.content.Intent;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -34,7 +39,6 @@ import com.android.internal.R;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
@@ -104,6 +108,7 @@ public class ChooseTypeAndAccountActivity extends Activity
private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts";
private static final String KEY_INSTANCE_STATE_SELECTED_ACCOUNT_NAME = "selectedAccountName";
private static final String KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT = "selectedAddAccount";
+ private static final String KEY_INSTANCE_STATE_ACCOUNT_LIST = "accountList";
private static final int SELECTED_ITEM_NONE = -1;
@@ -119,6 +124,9 @@ public class ChooseTypeAndAccountActivity extends Activity
private Parcelable[] mExistingAccounts = null;
private int mSelectedItemIndex;
private Button mOkButton;
+ private int mCallingUid;
+ private String mCallingPackage;
+ private boolean mDisallowAddAccounts;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -128,6 +136,24 @@ public class ChooseTypeAndAccountActivity extends Activity
+ savedInstanceState + ")");
}
+ String message = null;
+
+ try {
+ IBinder activityToken = getActivityToken();
+ mCallingUid = ActivityManagerNative.getDefault().getLaunchedFromUid(activityToken);
+ mCallingPackage = ActivityManagerNative.getDefault().getLaunchedFromPackage(
+ activityToken);
+ if (mCallingUid != 0 && mCallingPackage != null) {
+ Bundle restrictions = UserManager.get(this)
+ .getUserRestrictions(new UserHandle(UserHandle.getUserId(mCallingUid)));
+ mDisallowAddAccounts =
+ restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false);
+ }
+ } catch (RemoteException re) {
+ // Couldn't figure out caller details
+ Log.w(getClass().getSimpleName(), "Unable to get caller identity \n" + re);
+ }
+
// save some items we use frequently
final Intent intent = getIntent();
@@ -142,6 +168,7 @@ public class ChooseTypeAndAccountActivity extends Activity
mSelectedAddNewAccount = savedInstanceState.getBoolean(
KEY_INSTANCE_STATE_SELECTED_ADD_ACCOUNT, false);
+ mAccounts = savedInstanceState.getParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST);
} else {
mPendingRequest = REQUEST_NULL;
mExistingAccounts = null;
@@ -179,6 +206,11 @@ public class ChooseTypeAndAccountActivity extends Activity
// If there are no relevant accounts and only one relevant account type go directly to
// add account. Otherwise let the user choose.
if (mAccounts.isEmpty()) {
+ if (mDisallowAddAccounts) {
+ setContentView(R.layout.app_not_authorized);
+ setTitle(R.string.error_message_title);
+ return;
+ }
if (mSetOfRelevantAccountTypes.size() == 1) {
runAddAccountForAuthenticator(mSetOfRelevantAccountTypes.iterator().next());
} else {
@@ -234,6 +266,7 @@ public class ChooseTypeAndAccountActivity extends Activity
mAccounts.get(mSelectedItemIndex).name);
}
}
+ outState.putParcelableArrayList(KEY_INSTANCE_STATE_ACCOUNT_LIST, mAccounts);
}
public void onCancelButtonClicked(View view) {
@@ -296,7 +329,8 @@ public class ChooseTypeAndAccountActivity extends Activity
}
if (accountName == null || accountType == null) {
- Account[] currentAccounts = AccountManager.get(this).getAccounts();
+ Account[] currentAccounts = AccountManager.get(this).getAccountsForPackage(
+ mCallingPackage, mCallingUid);
Set<Account> preExistingAccounts = new HashSet<Account>();
for (Parcelable accountParcel : mExistingAccounts) {
preExistingAccounts.add((Account) accountParcel);
@@ -347,7 +381,8 @@ public class ChooseTypeAndAccountActivity extends Activity
AccountManager.KEY_INTENT);
if (intent != null) {
mPendingRequest = REQUEST_ADD_ACCOUNT;
- mExistingAccounts = AccountManager.get(this).getAccounts();
+ mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
+ mCallingUid);
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent, REQUEST_ADD_ACCOUNT);
return;
@@ -424,12 +459,14 @@ public class ChooseTypeAndAccountActivity extends Activity
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];
+ String[] listItems = new String[accounts.size() + (mDisallowAddAccounts ? 0 : 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);
+ if (!mDisallowAddAccounts) {
+ listItems[accounts.size()] = getResources().getString(
+ R.string.add_account_button_label);
+ }
return listItems;
}
@@ -439,7 +476,8 @@ public class ChooseTypeAndAccountActivity extends Activity
* allowable accounts, if provided.
*/
private ArrayList<Account> getAcceptableAccountChoices(AccountManager accountManager) {
- final Account[] accounts = accountManager.getAccounts();
+ final Account[] accounts = accountManager.getAccountsForPackage(mCallingPackage,
+ mCallingUid);
ArrayList<Account> accountsToPopulate = new ArrayList<Account>(accounts.length);
for (Account account : accounts) {
if (mSetOfAllowableAccounts != null
diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl
index 8860710..58612da 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -70,4 +70,17 @@ oneway interface IAccountAuthenticator {
* Gets whether or not the account is allowed to be removed.
*/
void getAccountRemovalAllowed(in IAccountAuthenticatorResponse response, in Account account);
+
+ /**
+ * Returns a Bundle containing the required credentials to copy the account across users.
+ */
+ void getAccountCredentialsForCloning(in IAccountAuthenticatorResponse response,
+ in Account account);
+
+ /**
+ * Uses the Bundle containing credentials from another instance of the authenticator to create
+ * a copy of the account on this user.
+ */
+ void addAccountFromCredentials(in IAccountAuthenticatorResponse response, in Account account,
+ in Bundle accountCredentials);
}
diff --git a/core/java/android/accounts/IAccountAuthenticatorCache.java b/core/java/android/accounts/IAccountAuthenticatorCache.java
deleted file mode 100644
index 06c2106..0000000
--- a/core/java/android/accounts/IAccountAuthenticatorCache.java
+++ /dev/null
@@ -1,66 +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.accounts;
-
-import android.content.pm.RegisteredServicesCache;
-import android.content.pm.RegisteredServicesCacheListener;
-import android.os.Handler;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Collection;
-
-/**
- * An interface to the Authenticator specialization of RegisteredServicesCache. The use of
- * this interface by the AccountManagerService makes it easier to unit test it.
- * @hide
- */
-public interface IAccountAuthenticatorCache {
- /**
- * Accessor for the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that
- * matched the specified {@link android.accounts.AuthenticatorDescription} or null
- * if none match.
- * @param type the authenticator type to return
- * @return the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that
- * matches the account type or null if none is present
- */
- RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> getServiceInfo(
- AuthenticatorDescription type, int userId);
-
- /**
- * @return A copy of a Collection of all the current Authenticators.
- */
- Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> getAllServices(
- int userId);
-
- /**
- * Dumps the state of the cache. See
- * {@link android.os.Binder#dump(java.io.FileDescriptor, java.io.PrintWriter, String[])}
- */
- void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId);
-
- /**
- * Sets a listener that will be notified whenever the authenticator set changes
- * @param listener the listener to notify, or null
- * @param handler the {@link Handler} on which the notification will be posted. If null
- * the notification will be posted on the main thread.
- */
- void setListener(RegisteredServicesCacheListener<AuthenticatorDescription> listener,
- Handler handler);
-
- void invalidateCache(int userId);
-}
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index dbb4924..86e279f 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -31,10 +31,12 @@ interface IAccountManager {
String getUserData(in Account account, String key);
AuthenticatorDescription[] getAuthenticatorTypes();
Account[] getAccounts(String accountType);
+ Account[] getAccountsForPackage(String packageName, int uid);
+ Account[] getAccountsByTypeForPackage(String type, String packageName);
Account[] getAccountsAsUser(String accountType, int userId);
void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features);
void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features);
- boolean addAccount(in Account account, String password, in Bundle extras);
+ boolean addAccountExplicitly(in Account account, String password, in Bundle extras);
void removeAccount(in IAccountManagerResponse response, in Account account);
void invalidateAuthToken(String accountType, String authToken);
String peekAuthToken(in Account account, String authTokenType);
@@ -47,7 +49,7 @@ interface IAccountManager {
void getAuthToken(in IAccountManagerResponse response, in Account account,
String authTokenType, boolean notifyOnAuthFailure, boolean expectActivityLaunch,
in Bundle options);
- void addAcount(in IAccountManagerResponse response, String accountType,
+ void addAccount(in IAccountManagerResponse response, String accountType,
String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch,
in Bundle options);
void updateCredentials(in IAccountManagerResponse response, in Account account,
@@ -58,4 +60,9 @@ interface IAccountManager {
in Bundle options, boolean expectActivityLaunch, int userId);
void getAuthTokenLabel(in IAccountManagerResponse response, String accountType,
String authTokenType);
+
+ /* Shared accounts */
+ boolean addSharedAccountAsUser(in Account account, int userId);
+ Account[] getSharedAccountsAsUser(int userId);
+ boolean removeSharedAccountAsUser(in Account account, int userId);
}
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 788765d..39eb8d6 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -24,7 +24,6 @@ import java.util.ArrayList;
*/
public abstract class Animator implements Cloneable {
-
/**
* The set of listeners to be sent events through the life of an animation.
*/
@@ -70,47 +69,55 @@ public abstract class Animator implements Cloneable {
}
/**
- * The amount of time, in milliseconds, to delay starting the animation after
- * {@link #start()} is called.
+ * The amount of time, in milliseconds, to delay processing the animation
+ * after {@link #start()} is called.
*
* @return the number of milliseconds to delay running the animation
*/
public abstract long getStartDelay();
/**
- * The amount of time, in milliseconds, to delay starting the animation after
- * {@link #start()} is called.
+ * The amount of time, in milliseconds, to delay processing the animation
+ * after {@link #start()} is called.
* @param startDelay The amount of the delay, in milliseconds
*/
public abstract void setStartDelay(long startDelay);
-
/**
- * Sets the length of the animation.
+ * Sets the duration of the animation.
*
* @param duration The length of the animation, in milliseconds.
*/
public abstract Animator setDuration(long duration);
/**
- * Gets the length of the animation.
+ * Gets the duration of the animation.
*
* @return The length of the animation, in milliseconds.
*/
public abstract long getDuration();
/**
- * The time interpolator used in calculating the elapsed fraction of this animation. The
- * interpolator determines whether the animation runs with linear or non-linear motion,
- * such as acceleration and deceleration. The default value is
- * {@link android.view.animation.AccelerateDecelerateInterpolator}
+ * The time interpolator used in calculating the elapsed fraction of the
+ * animation. The interpolator determines whether the animation runs with
+ * linear or non-linear motion, such as acceleration and deceleration. The
+ * default value is {@link android.view.animation.AccelerateDecelerateInterpolator}.
*
* @param value the interpolator to be used by this animation
*/
public abstract void setInterpolator(TimeInterpolator value);
/**
+ * Returns the timing interpolator that this animation uses.
+ *
+ * @return The timing interpolator for this animation.
+ */
+ public TimeInterpolator getInterpolator() {
+ return null;
+ }
+
+ /**
* Returns whether this Animator is currently running (having been started and gone past any
* initial startDelay period and not yet ended).
*
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index ed4036d..d753e32 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -185,7 +185,7 @@ public class AnimatorInflater {
TypedArray a =
context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Animator);
- long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 0);
+ long duration = a.getInt(com.android.internal.R.styleable.Animator_duration, 300);
long startDelay = a.getInt(com.android.internal.R.styleable.Animator_startOffset, 0);
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index f9fa444..b48853b 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -120,9 +120,19 @@ public final class AnimatorSet extends Animator {
// set, it is passed along to the child animations.
private long mDuration = -1;
+ // Records the interpolator for the set. Null value indicates that no interpolator
+ // was set on this AnimatorSet, so it should not be passed down to the children.
+ private TimeInterpolator mInterpolator = null;
+
/**
* Sets up this AnimatorSet to play all of the supplied animations at the same time.
+ * This is equivalent to calling {@link #play(Animator)} with the first animator in the
+ * set and then {@link Builder#with(Animator)} with each of the other animators. Note that
+ * an Animator with a {@link Animator#setStartDelay(long) startDelay} will not actually
+ * start until that delay elapses, which means that if the first animator in the list
+ * supplied to this constructor has a startDelay, none of the other animators will start
+ * until that first animator's startDelay has elapsed.
*
* @param items The animations that will be started simultaneously.
*/
@@ -230,15 +240,21 @@ public final class AnimatorSet extends Animator {
/**
* Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations}
- * of this AnimatorSet.
+ * of this AnimatorSet. The default value is null, which means that no interpolator
+ * is set on this AnimatorSet. Setting the interpolator to any non-null value
+ * will cause that interpolator to be set on the child animations
+ * when the set is started.
*
* @param interpolator the interpolator to be used by each child animation of this AnimatorSet
*/
@Override
public void setInterpolator(TimeInterpolator interpolator) {
- for (Node node : mNodes) {
- node.animation.setInterpolator(interpolator);
- }
+ mInterpolator = interpolator;
+ }
+
+ @Override
+ public TimeInterpolator getInterpolator() {
+ return mInterpolator;
}
/**
@@ -460,7 +476,12 @@ public final class AnimatorSet extends Animator {
node.animation.setDuration(mDuration);
}
}
- // First, sort the nodes (if necessary). This will ensure that sortedNodes
+ if (mInterpolator != null) {
+ for (Node node : mNodes) {
+ node.animation.setInterpolator(mInterpolator);
+ }
+ }
+ // First, sort the nodes (if necessary). This will ensure that sortedNodes
// contains the animation nodes in the correct order.
sortNodes();
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index 6172aab..4026f7f 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -21,6 +21,7 @@ import java.util.Arrays;
import android.animation.Keyframe.IntKeyframe;
import android.animation.Keyframe.FloatKeyframe;
import android.animation.Keyframe.ObjectKeyframe;
+import android.util.Log;
/**
* This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate
@@ -56,24 +57,36 @@ class KeyframeSet {
} else {
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
- keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
+ keyframes[i] =
+ (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
}
}
return new IntKeyframeSet(keyframes);
}
public static KeyframeSet ofFloat(float... values) {
+ boolean badValue = false;
int numKeyframes = values.length;
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
+ if (Float.isNaN(values[0])) {
+ badValue = true;
+ }
} else {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
- keyframes[i] = (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
+ keyframes[i] =
+ (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
+ if (Float.isNaN(values[i])) {
+ badValue = true;
+ }
}
}
+ if (badValue) {
+ Log.w("Animator", "Bad value (NaN) in float animator");
+ }
return new FloatKeyframeSet(keyframes);
}
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 0372cb0..173ee73 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -19,7 +19,6 @@ package android.animation;
import android.util.Log;
import android.util.Property;
-import java.lang.reflect.Method;
import java.util.ArrayList;
/**
@@ -49,6 +48,8 @@ public final class ObjectAnimator extends ValueAnimator {
private Property mProperty;
+ private boolean mAutoCancel = false;
+
/**
* Sets the name of the property that will be animated. This name is used to derive
* a setter function that will be called to set animated values.
@@ -346,17 +347,83 @@ public final class ObjectAnimator extends ValueAnimator {
// No values yet - this animator is being constructed piecemeal. Init the values with
// whatever the current propertyName is
if (mProperty != null) {
- setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator)null, values));
+ setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator) null, values));
} else {
- setValues(PropertyValuesHolder.ofObject(mPropertyName, (TypeEvaluator)null, values));
+ setValues(PropertyValuesHolder.ofObject(mPropertyName,
+ (TypeEvaluator) null, values));
}
} else {
super.setObjectValues(values);
}
}
+ /**
+ * autoCancel controls whether an ObjectAnimator will be canceled automatically
+ * when any other ObjectAnimator with the same target and properties is started.
+ * Setting this flag may make it easier to run different animators on the same target
+ * object without having to keep track of whether there are conflicting animators that
+ * need to be manually canceled. Canceling animators must have the same exact set of
+ * target properties, in the same order.
+ *
+ * @param cancel Whether future ObjectAnimators with the same target and properties
+ * as this ObjectAnimator will cause this ObjectAnimator to be canceled.
+ */
+ public void setAutoCancel(boolean cancel) {
+ mAutoCancel = cancel;
+ }
+
+ private boolean hasSameTargetAndProperties(Animator anim) {
+ if (anim instanceof ObjectAnimator) {
+ PropertyValuesHolder[] theirValues = ((ObjectAnimator) anim).getValues();
+ if (((ObjectAnimator) anim).getTarget() == mTarget &&
+ mValues.length == theirValues.length) {
+ for (int i = 0; i < mValues.length; ++i) {
+ PropertyValuesHolder pvhMine = mValues[i];
+ PropertyValuesHolder pvhTheirs = theirValues[i];
+ if (pvhMine.getPropertyName() == null ||
+ !pvhMine.getPropertyName().equals(pvhTheirs.getPropertyName())) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public void start() {
+ // See if any of the current active/pending animators need to be canceled
+ AnimationHandler handler = sAnimationHandler.get();
+ if (handler != null) {
+ int numAnims = handler.mAnimations.size();
+ for (int i = numAnims - 1; i >= 0; i--) {
+ if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
+ ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
+ if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
+ anim.cancel();
+ }
+ }
+ }
+ numAnims = handler.mPendingAnimations.size();
+ for (int i = numAnims - 1; i >= 0; i--) {
+ if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {
+ ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);
+ if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
+ anim.cancel();
+ }
+ }
+ }
+ numAnims = handler.mDelayedAnims.size();
+ for (int i = numAnims - 1; i >= 0; i--) {
+ if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {
+ ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);
+ if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
+ anim.cancel();
+ }
+ }
+ }
+ }
if (DBG) {
Log.d("ObjectAnimator", "Anim target, duration: " + mTarget + ", " + getDuration());
for (int i = 0; i < mValues.length; ++i) {
diff --git a/core/java/android/animation/RectEvaluator.java b/core/java/android/animation/RectEvaluator.java
new file mode 100644
index 0000000..28d496b
--- /dev/null
+++ b/core/java/android/animation/RectEvaluator.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.animation;
+
+import android.graphics.Rect;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>Rect</code> values.
+ */
+public class RectEvaluator implements TypeEvaluator<Rect> {
+
+ /**
+ * This function returns the result of linearly interpolating the start and
+ * end Rect values, with <code>fraction</code> representing the proportion
+ * between the start and end values. The calculation is a simple parametric
+ * calculation on each of the separate components in the Rect objects
+ * (left, top, right, and bottom).
+ *
+ * @param fraction The fraction from the starting to the ending values
+ * @param startValue The start Rect
+ * @param endValue The end Rect
+ * @return A linear interpolation between the start and end values, given the
+ * <code>fraction</code> parameter.
+ */
+ @Override
+ public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
+ return new Rect(startValue.left + (int)((endValue.left - startValue.left) * fraction),
+ startValue.top + (int)((endValue.top - startValue.top) * fraction),
+ startValue.right + (int)((endValue.right - startValue.right) * fraction),
+ startValue.bottom + (int)((endValue.bottom - startValue.bottom) * fraction));
+ }
+}
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index f7460c4..cb44264 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -16,10 +16,8 @@
package android.animation;
-import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
-import android.os.SystemProperties;
+import android.os.Trace;
import android.util.AndroidRuntimeException;
import android.view.Choreographer;
import android.view.animation.AccelerateDecelerateInterpolator;
@@ -48,6 +46,7 @@ import java.util.HashMap;
* Animation</a> developer guide.</p>
* </div>
*/
+@SuppressWarnings("unchecked")
public class ValueAnimator extends Animator {
/**
@@ -83,7 +82,10 @@ public class ValueAnimator extends Animator {
// The static sAnimationHandler processes the internal timing loop on which all animations
// are based
- private static ThreadLocal<AnimationHandler> sAnimationHandler =
+ /**
+ * @hide
+ */
+ protected static ThreadLocal<AnimationHandler> sAnimationHandler =
new ThreadLocal<AnimationHandler>();
// The time interpolator to be used if none is set on the animation
@@ -337,7 +339,7 @@ public class ValueAnimator extends Animator {
return;
}
if (mValues == null || mValues.length == 0) {
- setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofInt("", values)});
+ setValues(PropertyValuesHolder.ofInt("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setIntValues(values);
@@ -365,7 +367,7 @@ public class ValueAnimator extends Animator {
return;
}
if (mValues == null || mValues.length == 0) {
- setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofFloat("", values)});
+ setValues(PropertyValuesHolder.ofFloat("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setFloatValues(values);
@@ -397,8 +399,7 @@ public class ValueAnimator extends Animator {
return;
}
if (mValues == null || mValues.length == 0) {
- setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofObject("",
- (TypeEvaluator)null, values)});
+ setValues(PropertyValuesHolder.ofObject("", null, values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setObjectValues(values);
@@ -420,7 +421,7 @@ public class ValueAnimator extends Animator {
mValues = values;
mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
for (int i = 0; i < numValues; ++i) {
- PropertyValuesHolder valuesHolder = (PropertyValuesHolder) values[i];
+ PropertyValuesHolder valuesHolder = values[i];
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
}
// New property/values/target should cause re-initialization prior to starting
@@ -531,22 +532,28 @@ public class ValueAnimator extends Animator {
* animations possible.
*
* The handler uses the Choreographer for executing periodic callbacks.
+ *
+ * @hide
*/
- private static class AnimationHandler implements Runnable {
+ @SuppressWarnings("unchecked")
+ protected static class AnimationHandler implements Runnable {
// The per-thread list of all active animations
- private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
+ /** @hide */
+ protected 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>();
+ /** @hide */
+ protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();
/**
* Internal per-thread collections used to avoid set collisions as animations start and end
* while being processed.
+ * @hide
*/
- private final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
+ protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();
private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();
@@ -846,6 +853,7 @@ public class ValueAnimator extends Animator {
*
* @return The timing interpolator for this ValueAnimator.
*/
+ @Override
public TimeInterpolator getInterpolator() {
return mInterpolator;
}
@@ -1015,6 +1023,9 @@ public class ValueAnimator extends Animator {
mRunning = false;
mStarted = false;
mStartListenersCalled = false;
+ mPlayingBackwards = false;
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "animator",
+ System.identityHashCode(this));
}
/**
@@ -1022,6 +1033,8 @@ public class ValueAnimator extends Animator {
* called on the UI thread.
*/
private void startAnimation(AnimationHandler handler) {
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "animator",
+ System.identityHashCode(this));
initAnimation();
handler.mAnimations.add(this);
if (mStartDelay > 0 && mListeners != null) {
@@ -1086,7 +1099,7 @@ public class ValueAnimator extends Animator {
}
}
if (mRepeatMode == REVERSE) {
- mPlayingBackwards = mPlayingBackwards ? false : true;
+ mPlayingBackwards = !mPlayingBackwards;
}
mCurrentIteration += (int)fraction;
fraction = fraction % 1f;
@@ -1243,7 +1256,7 @@ public class ValueAnimator extends Animator {
}
}
- private AnimationHandler getOrCreateAnimationHandler() {
+ private static AnimationHandler getOrCreateAnimationHandler() {
AnimationHandler handler = sAnimationHandler.get();
if (handler == null) {
handler = new AnimationHandler();
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 3602fc4..c4ddf1f 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -695,6 +695,86 @@ public abstract class ActionBar {
public boolean isTitleTruncated() { return false; }
/**
+ * Set an alternate drawable to display next to the icon/logo/title
+ * when {@link #DISPLAY_HOME_AS_UP} is enabled. This can be useful if you are using
+ * this mode to display an alternate selection for up navigation, such as a sliding drawer.
+ *
+ * <p>If you pass <code>null</code> to this method, the default drawable from the theme
+ * will be used.</p>
+ *
+ * <p>If you implement alternate or intermediate behavior around Up, you should also
+ * call {@link #setHomeActionContentDescription(int) setHomeActionContentDescription()}
+ * to provide a correct description of the action for accessibility support.</p>
+ *
+ * @param indicator A drawable to use for the up indicator, or null to use the theme's default
+ *
+ * @see #setDisplayOptions(int, int)
+ * @see #setDisplayHomeAsUpEnabled(boolean)
+ * @see #setHomeActionContentDescription(int)
+ */
+ public void setHomeAsUpIndicator(Drawable indicator) { }
+
+ /**
+ * Set an alternate drawable to display next to the icon/logo/title
+ * when {@link #DISPLAY_HOME_AS_UP} is enabled. This can be useful if you are using
+ * this mode to display an alternate selection for up navigation, such as a sliding drawer.
+ *
+ * <p>If you pass <code>0</code> to this method, the default drawable from the theme
+ * will be used.</p>
+ *
+ * <p>If you implement alternate or intermediate behavior around Up, you should also
+ * call {@link #setHomeActionContentDescription(int) setHomeActionContentDescription()}
+ * to provide a correct description of the action for accessibility support.</p>
+ *
+ * @param resId Resource ID of a drawable to use for the up indicator, or null
+ * to use the theme's default
+ *
+ * @see #setDisplayOptions(int, int)
+ * @see #setDisplayHomeAsUpEnabled(boolean)
+ * @see #setHomeActionContentDescription(int)
+ */
+ public void setHomeAsUpIndicator(int resId) { }
+
+ /**
+ * Set an alternate description for the Home/Up action, when enabled.
+ *
+ * <p>This description is commonly used for accessibility/screen readers when
+ * the Home action is enabled. (See {@link #setDisplayHomeAsUpEnabled(boolean)}.)
+ * Examples of this are, "Navigate Home" or "Navigate Up" depending on the
+ * {@link #DISPLAY_HOME_AS_UP} display option. If you have changed the home-as-up
+ * indicator using {@link #setHomeAsUpIndicator(int)} to indicate more specific
+ * functionality such as a sliding drawer, you should also set this to accurately
+ * describe the action.</p>
+ *
+ * <p>Setting this to <code>null</code> will use the system default description.</p>
+ *
+ * @param description New description for the Home action when enabled
+ * @see #setHomeAsUpIndicator(int)
+ * @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable)
+ */
+ public void setHomeActionContentDescription(CharSequence description) { }
+
+ /**
+ * Set an alternate description for the Home/Up action, when enabled.
+ *
+ * <p>This description is commonly used for accessibility/screen readers when
+ * the Home action is enabled. (See {@link #setDisplayHomeAsUpEnabled(boolean)}.)
+ * Examples of this are, "Navigate Home" or "Navigate Up" depending on the
+ * {@link #DISPLAY_HOME_AS_UP} display option. If you have changed the home-as-up
+ * indicator using {@link #setHomeAsUpIndicator(int)} to indicate more specific
+ * functionality such as a sliding drawer, you should also set this to accurately
+ * describe the action.</p>
+ *
+ * <p>Setting this to <code>0</code> will use the system default description.</p>
+ *
+ * @param resId Resource ID of a string to use as the new description
+ * for the Home action when enabled
+ * @see #setHomeAsUpIndicator(int)
+ * @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable)
+ */
+ public void setHomeActionContentDescription(int resId) { }
+
+ /**
* Listener interface for ActionBar navigation events.
*/
public interface OnNavigationListener {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d6ddeb6..6b5df7f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1346,6 +1346,20 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * This is called when the user is requesting an assist, to build a full
+ * {@link Intent#ACTION_ASSIST} Intent with all of the context of the current
+ * application. You can override this method to place into the bundle anything
+ * you would like to appear in the {@link Intent#EXTRA_ASSIST_CONTEXT} part
+ * of the assist Intent. The default implementation does nothing.
+ *
+ * <p>This function will be called after any global assist callbacks that had
+ * been registered with {@link Application#registerOnProvideAssistDataListener
+ * Application.registerOnProvideAssistDataListener}.
+ */
+ public void onProvideAssistData(Bundle data) {
+ }
+
+ /**
* Called when you are no longer visible to the user. You will next
* receive either {@link #onRestart}, {@link #onDestroy}, or nothing,
* depending on later user activity.
@@ -3499,7 +3513,8 @@ public class Activity extends ContextThemeWrapper
try {
String resolvedType = null;
if (fillInIntent != null) {
- fillInIntent.setAllowFds(false);
+ fillInIntent.migrateExtraStreamToClipData();
+ fillInIntent.prepareToLeaveProcess();
resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver());
}
int result = ActivityManagerNative.getDefault()
@@ -3724,9 +3739,10 @@ public class Activity extends ContextThemeWrapper
if (mParent == null) {
int result = ActivityManager.START_RETURN_INTENT_TO_CALLER;
try {
- intent.setAllowFds(false);
+ intent.migrateExtraStreamToClipData();
+ intent.prepareToLeaveProcess();
result = ActivityManagerNative.getDefault()
- .startActivity(mMainThread.getApplicationThread(),
+ .startActivity(mMainThread.getApplicationThread(), getBasePackageName(),
intent, intent.resolveTypeIfNeeded(getContentResolver()),
mToken, mEmbeddedID, requestCode,
ActivityManager.START_FLAG_ONLY_IF_NEEDED, null, null,
@@ -3794,7 +3810,8 @@ public class Activity extends ContextThemeWrapper
public boolean startNextMatchingActivity(Intent intent, Bundle options) {
if (mParent == null) {
try {
- intent.setAllowFds(false);
+ intent.migrateExtraStreamToClipData();
+ intent.prepareToLeaveProcess();
return ActivityManagerNative.getDefault()
.startNextMatchingActivity(mToken, intent, options);
} catch (RemoteException e) {
@@ -4013,10 +4030,16 @@ public class Activity extends ContextThemeWrapper
* use this information to validate that the recipient is allowed to
* receive the data.
*
- * <p>Note: if the calling activity is not expecting a result (that is it
+ * <p class="note">Note: if the calling activity is not expecting a result (that is it
* did not use the {@link #startActivityForResult}
* form that includes a request code), then the calling package will be
- * null.
+ * null.</p>
+ *
+ * <p class="note">Note: prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * the result from this method was unstable. If the process hosting the calling
+ * package was no longer running, it would return null instead of the proper package
+ * name. You can use {@link #getCallingActivity()} and retrieve the package name
+ * from that instead.</p>
*
* @return The package of the activity that will receive your
* reply, or null if none.
@@ -4035,12 +4058,12 @@ public class Activity extends ContextThemeWrapper
* can use this information to validate that the recipient is allowed to
* receive the data.
*
- * <p>Note: if the calling activity is not expecting a result (that is it
+ * <p class="note">Note: if the calling activity is not expecting a result (that is it
* did not use the {@link #startActivityForResult}
* form that includes a request code), then the calling package will be
* null.
*
- * @return String The full name of the activity that will receive your
+ * @return The ComponentName of the activity that will receive your
* reply, or null if none.
*/
public ComponentName getCallingActivity() {
@@ -4148,7 +4171,7 @@ public class Activity extends ContextThemeWrapper
if (false) Log.v(TAG, "Finishing self: token=" + mToken);
try {
if (resultData != null) {
- resultData.setAllowFds(false);
+ resultData.prepareToLeaveProcess();
}
if (ActivityManagerNative.getDefault()
.finishActivity(mToken, resultCode, resultData)) {
@@ -4300,7 +4323,7 @@ public class Activity extends ContextThemeWrapper
int flags) {
String packageName = getPackageName();
try {
- data.setAllowFds(false);
+ data.prepareToLeaveProcess();
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName,
@@ -4979,9 +5002,10 @@ public class Activity extends ContextThemeWrapper
resultData = mResultData;
}
if (resultData != null) {
- resultData.setAllowFds(false);
+ resultData.prepareToLeaveProcess();
}
try {
+ upIntent.prepareToLeaveProcess();
return ActivityManagerNative.getDefault().navigateUpTo(mToken, upIntent,
resultCode, resultData);
} catch (RemoteException e) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 594be68..a25e311 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -16,6 +16,7 @@
package android.app;
+import android.R;
import com.android.internal.app.IUsageStats;
import com.android.internal.os.PkgUsageStats;
import com.android.internal.util.MemInfoReader;
@@ -369,9 +370,9 @@ public class ActivityManager {
// Really brain dead right now -- just take this from the configured
// vm heap size, and assume it is in megabytes and thus ends with "m".
String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m");
- return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));
+ return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length() - 1));
}
-
+
/**
* Used by persistent processes to determine if they are running on a
* higher-end device so should be okay using hardware drawing acceleration
@@ -896,7 +897,7 @@ public class ActivityManager {
* @param taskId The identifier of the task to be moved, as found in
* {@link RunningTaskInfo} or {@link RecentTaskInfo}.
* @param flags Additional operational flags, 0 or more of
- * {@link #MOVE_TASK_WITH_HOME}.
+ * {@link #MOVE_TASK_WITH_HOME}, {@link #MOVE_TASK_NO_USER_ACTION}.
*/
public void moveTaskToFront(int taskId, int flags) {
moveTaskToFront(taskId, flags, null);
@@ -911,7 +912,7 @@ public class ActivityManager {
* @param taskId The identifier of the task to be moved, as found in
* {@link RunningTaskInfo} or {@link RecentTaskInfo}.
* @param flags Additional operational flags, 0 or more of
- * {@link #MOVE_TASK_WITH_HOME}.
+ * {@link #MOVE_TASK_WITH_HOME}, {@link #MOVE_TASK_NO_USER_ACTION}.
* @param options Additional options for the operation, either null or
* as per {@link Context#startActivity(Intent, android.os.Bundle)
* Context.startActivity(Intent, Bundle)}.
@@ -1915,7 +1916,30 @@ public class ActivityManager {
return PackageManager.PERMISSION_DENIED;
}
- /** @hide */
+ /**
+ * @hide
+ * Helper for dealing with incoming user arguments to system service calls.
+ * Takes care of checking permissions and converting USER_CURRENT to the
+ * actual current user.
+ *
+ * @param callingPid The pid of the incoming call, as per Binder.getCallingPid().
+ * @param callingUid The uid of the incoming call, as per Binder.getCallingUid().
+ * @param userId The user id argument supplied by the caller -- this is the user
+ * they want to run as.
+ * @param allowAll If true, we will allow USER_ALL. This means you must be prepared
+ * to get a USER_ALL returned and deal with it correctly. If false,
+ * an exception will be thrown if USER_ALL is supplied.
+ * @param requireFull If true, the caller must hold
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} to be able to run as a
+ * different user than their current process; otherwise they must hold
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS}.
+ * @param name Optional textual name of the incoming call; only for generating error messages.
+ * @param callerPackage Optional package name of caller; only for error messages.
+ *
+ * @return Returns the user ID that the call should run as. Will always be a concrete
+ * user number, unless <var>allowAll</var> is true in which case it could also be
+ * USER_ALL.
+ */
public static int handleIncomingUser(int callingPid, int callingUid, int userId,
boolean allowAll, boolean requireFull, String name, String callerPackage) {
if (UserHandle.getUserId(callingUid) == userId) {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index fe7338b..d4478bf 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -39,7 +39,6 @@ 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;
@@ -93,7 +92,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
try {
getDefault().broadcastIntent(
null, intent, null, null, Activity.RESULT_OK, null, null,
- null /*permission*/, false, true, userId);
+ null /*permission*/, AppOpsManager.OP_NONE, false, true, userId);
} catch (RemoteException ex) {
}
}
@@ -117,6 +116,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
@@ -128,7 +128,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
? data.readFileDescriptor() : null;
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
- int result = startActivity(app, intent, resolvedType,
+ int result = startActivity(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags,
profileFile, profileFd, options);
reply.writeNoException();
@@ -141,6 +141,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
@@ -153,7 +154,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
int userId = data.readInt();
- int result = startActivityAsUser(app, intent, resolvedType,
+ int result = startActivityAsUser(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags,
profileFile, profileFd, options, userId);
reply.writeNoException();
@@ -166,6 +167,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
@@ -178,7 +180,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
int userId = data.readInt();
- WaitResult result = startActivityAndWait(app, intent, resolvedType,
+ WaitResult result = startActivityAndWait(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags,
profileFile, profileFd, options, userId);
reply.writeNoException();
@@ -191,6 +193,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
@@ -201,7 +204,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
int userId = data.readInt();
- int result = startActivityWithConfig(app, intent, resolvedType,
+ int result = startActivityWithConfig(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, config, options, userId);
reply.writeNoException();
reply.writeInt(result);
@@ -341,11 +344,12 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
String resultData = data.readString();
Bundle resultExtras = data.readBundle();
String perm = data.readString();
+ int appOp = data.readInt();
boolean serialized = data.readInt() != 0;
boolean sticky = data.readInt() != 0;
int userId = data.readInt();
int res = broadcastIntent(app, intent, resolvedType, resultTo,
- resultCode, resultData, resultExtras, perm,
+ resultCode, resultData, resultExtras, perm, appOp,
serialized, sticky, userId);
reply.writeNoException();
reply.writeInt(res);
@@ -836,8 +840,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Bundle arguments = data.readBundle();
IBinder b = data.readStrongBinder();
IInstrumentationWatcher w = IInstrumentationWatcher.Stub.asInterface(b);
+ b = data.readStrongBinder();
+ IUiAutomationConnection c = IUiAutomationConnection.Stub.asInterface(b);
int userId = data.readInt();
- boolean res = startInstrumentation(className, profileFile, fl, arguments, w, userId);
+ boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -1407,6 +1413,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case SET_USER_IS_MONKEY_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final boolean monkey = (data.readInt() == 1);
+ setUserIsMonkey(monkey);
+ reply.writeNoException();
+ return true;
+ }
+
case FINISH_HEAVY_WEIGHT_APP_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
finishHeavyWeightApp();
@@ -1526,13 +1540,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ String callingPackage = data.readString();
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 userId = data.readInt();
- int result = startActivities(app, intents, resolvedTypes, resultTo,
+ int result = startActivities(app, callingPackage, intents, resolvedTypes, resultTo,
options, userId);
reply.writeNoException();
reply.writeInt(result);
@@ -1784,6 +1799,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_LAUNCHED_FROM_PACKAGE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ String res = getLaunchedFromPackage(token);
+ reply.writeNoException();
+ reply.writeString(res);
+ return true;
+ }
+
case REGISTER_USER_SWITCH_OBSERVER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IUserSwitchObserver observer = IUserSwitchObserver.Stub.asInterface(
@@ -1819,6 +1843,42 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_TOP_ACTIVITY_EXTRAS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int requestType = data.readInt();
+ Bundle res = getTopActivityExtras(requestType);
+ reply.writeNoException();
+ reply.writeBundle(res);
+ return true;
+ }
+
+ case REPORT_TOP_ACTIVITY_EXTRAS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ Bundle extras = data.readBundle();
+ reportTopActivityExtras(token, extras);
+ reply.writeNoException();
+ return true;
+ }
+
+ case KILL_UID_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int uid = data.readInt();
+ String reason = data.readString();
+ killUid(uid, reason);
+ reply.writeNoException();
+ return true;
+ }
+
+ case HANG_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder who = data.readStrongBinder();
+ boolean allowRestart = data.readInt() != 0;
+ hang(who, allowRestart);
+ reply.writeNoException();
+ return true;
+ }
+
}
return super.onTransact(code, data, reply, flags);
@@ -1855,7 +1915,7 @@ class ActivityManagerProxy implements IActivityManager
return mRemote;
}
- public int startActivity(IApplicationThread caller, Intent intent,
+ public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
@@ -1863,6 +1923,7 @@ class ActivityManagerProxy implements IActivityManager
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+ data.writeString(callingPackage);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeStrongBinder(resultTo);
@@ -1890,7 +1951,7 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
- public int startActivityAsUser(IApplicationThread caller, Intent intent,
+ public int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException {
@@ -1898,6 +1959,7 @@ class ActivityManagerProxy implements IActivityManager
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+ data.writeString(callingPackage);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeStrongBinder(resultTo);
@@ -1925,14 +1987,15 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
return result;
}
- public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent,
- String resolvedType, IBinder resultTo, String resultWho,
+ public WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage,
+ 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);
+ data.writeString(callingPackage);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeStrongBinder(resultTo);
@@ -1960,14 +2023,15 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
return result;
}
- public int startActivityWithConfig(IApplicationThread caller, Intent intent,
- String resolvedType, IBinder resultTo, String resultWho,
+ public int startActivityWithConfig(IApplicationThread caller, String callingPackage,
+ Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, Configuration config,
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);
+ data.writeString(callingPackage);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeStrongBinder(resultTo);
@@ -2138,7 +2202,7 @@ class ActivityManagerProxy implements IActivityManager
public int broadcastIntent(IApplicationThread caller,
Intent intent, String resolvedType, IIntentReceiver resultTo,
int resultCode, String resultData, Bundle map,
- String requiredPermission, boolean serialized,
+ String requiredPermission, int appOp, boolean serialized,
boolean sticky, int userId) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -2152,6 +2216,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeString(resultData);
data.writeBundle(map);
data.writeString(requiredPermission);
+ data.writeInt(appOp);
data.writeInt(serialized ? 1 : 0);
data.writeInt(sticky ? 1 : 0);
data.writeInt(userId);
@@ -2857,8 +2922,8 @@ class ActivityManagerProxy implements IActivityManager
}
public boolean startInstrumentation(ComponentName className, String profileFile,
- int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId)
- throws RemoteException {
+ int flags, Bundle arguments, IInstrumentationWatcher watcher,
+ IUiAutomationConnection connection, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -2867,6 +2932,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(flags);
data.writeBundle(arguments);
data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
+ data.writeStrongBinder(connection != null ? connection.asBinder() : null);
data.writeInt(userId);
mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);
reply.readException();
@@ -3295,6 +3361,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeString(reason);
data.writeInt(secure ? 1 : 0);
mRemote.transact(KILL_PIDS_TRANSACTION, data, reply, 0);
+ reply.readException();
boolean res = reply.readInt() != 0;
data.recycle();
reply.recycle();
@@ -3583,7 +3650,18 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return res;
}
-
+
+ public void setUserIsMonkey(boolean monkey) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(monkey ? 1 : 0);
+ mRemote.transact(SET_USER_IS_MONKEY_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
public void finishHeavyWeightApp() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -3752,13 +3830,14 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
- public int startActivities(IApplicationThread caller,
+ public int startActivities(IApplicationThread caller, String callingPackage,
Intent[] intents, String[] resolvedTypes, IBinder resultTo,
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);
+ data.writeString(callingPackage);
data.writeTypedArray(intents, 0);
data.writeStringArray(resolvedTypes);
data.writeStrongBinder(resultTo);
@@ -4104,6 +4183,19 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
+ public String getLaunchedFromPackage(IBinder activityToken) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(activityToken);
+ mRemote.transact(GET_LAUNCHED_FROM_PACKAGE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ String result = reply.readString();
+ data.recycle();
+ reply.recycle();
+ return result;
+ }
+
public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -4150,5 +4242,54 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
+ public Bundle getTopActivityExtras(int requestType) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(requestType);
+ mRemote.transact(GET_TOP_ACTIVITY_EXTRAS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ Bundle res = reply.readBundle();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+
+ public void reportTopActivityExtras(IBinder token, Bundle extras) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ data.writeBundle(extras);
+ mRemote.transact(REPORT_TOP_ACTIVITY_EXTRAS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ public void killUid(int uid, String reason) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(uid);
+ data.writeString(reason);
+ mRemote.transact(KILL_UID_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ public void hang(IBinder who, boolean allowRestart) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(who);
+ data.writeInt(allowRestart ? 1 : 0);
+ mRemote.transact(HANG_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 22fd9a9..d4056c9 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -43,7 +43,6 @@ 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;
@@ -87,6 +86,7 @@ import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.renderscript.RenderScript;
+import android.security.AndroidKeyStoreProvider;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
@@ -187,7 +187,8 @@ public final class ActivityThread {
= new ArrayList<Application>();
// set of instantiated backup agents, keyed by package name
final HashMap<String, BackupAgent> mBackupAgents = new HashMap<String, BackupAgent>();
- static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal<ActivityThread>();
+ /** Reference to singleton {@link ActivityThread} */
+ private static ActivityThread sCurrentActivityThread;
Instrumentation mInstrumentation;
String mInstrumentationAppDir = null;
String mInstrumentationAppLibraryDir = null;
@@ -419,6 +420,7 @@ public final class ActivityThread {
ComponentName instrumentationName;
Bundle instrumentationArgs;
IInstrumentationWatcher instrumentationWatcher;
+ IUiAutomationConnection instrumentationUiAutomationConnection;
int debugMode;
boolean enableOpenGlTrace;
boolean restrictedBackupMode;
@@ -532,6 +534,12 @@ public final class ActivityThread {
String pkg;
CompatibilityInfo info;
}
+
+ static final class RequestActivityExtras {
+ IBinder activityToken;
+ IBinder requestToken;
+ int requestType;
+ }
private native void dumpGraphicsInfo(FileDescriptor fd);
@@ -723,9 +731,10 @@ public final class ActivityThread {
ComponentName instrumentationName, String profileFile,
ParcelFileDescriptor profileFd, boolean autoStopProfiler,
Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
- int debugMode, boolean enableOpenGlTrace, boolean isRestrictedBackupMode,
- boolean persistent, Configuration config, CompatibilityInfo compatInfo,
- Map<String, IBinder> services, Bundle coreSettings) {
+ IUiAutomationConnection instrumentationUiConnection, int debugMode,
+ boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent,
+ Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
+ Bundle coreSettings) {
if (services != null) {
// Setup the service cache in the ServiceManager
@@ -741,6 +750,7 @@ public final class ActivityThread {
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
+ data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableOpenGlTrace = enableOpenGlTrace;
data.restrictedBackupMode = isRestrictedBackupMode;
@@ -1107,6 +1117,16 @@ public final class ActivityThread {
queueOrSendMessage(H.UNSTABLE_PROVIDER_DIED, provider);
}
+ @Override
+ public void requestActivityExtras(IBinder activityToken, IBinder requestToken,
+ int requestType) {
+ RequestActivityExtras cmd = new RequestActivityExtras();
+ cmd.activityToken = activityToken;
+ cmd.requestToken = requestToken;
+ cmd.requestType = requestType;
+ queueOrSendMessage(H.REQUEST_ACTIVITY_EXTRAS, cmd);
+ }
+
private void printRow(PrintWriter pw, String format, Object...objs) {
pw.println(String.format(format, objs));
}
@@ -1172,6 +1192,7 @@ public final class ActivityThread {
public static final int TRIM_MEMORY = 140;
public static final int DUMP_PROVIDER = 141;
public static final int UNSTABLE_PROVIDER_DIED = 142;
+ public static final int REQUEST_ACTIVITY_EXTRAS = 143;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
@@ -1218,6 +1239,7 @@ public final class ActivityThread {
case TRIM_MEMORY: return "TRIM_MEMORY";
case DUMP_PROVIDER: return "DUMP_PROVIDER";
case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED";
+ case REQUEST_ACTIVITY_EXTRAS: return "REQUEST_ACTIVITY_EXTRAS";
}
}
return Integer.toString(code);
@@ -1429,6 +1451,9 @@ public final class ActivityThread {
case UNSTABLE_PROVIDER_DIED:
handleUnstableProviderDied((IBinder)msg.obj, false);
break;
+ case REQUEST_ACTIVITY_EXTRAS:
+ handleRequestActivityExtras((RequestActivityExtras)msg.obj);
+ break;
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
@@ -1564,12 +1589,18 @@ public final class ActivityThread {
}
public static ActivityThread currentActivityThread() {
- return sThreadLocal.get();
+ return sCurrentActivityThread;
}
public static String currentPackageName() {
ActivityThread am = currentActivityThread();
return (am != null && am.mBoundApplication != null)
+ ? am.mBoundApplication.appInfo.packageName : null;
+ }
+
+ public static String currentProcessName() {
+ ActivityThread am = currentActivityThread();
+ return (am != null && am.mBoundApplication != null)
? am.mBoundApplication.processName : null;
}
@@ -2321,6 +2352,23 @@ public final class ActivityThread {
performNewIntents(data.token, data.intents);
}
+ public void handleRequestActivityExtras(RequestActivityExtras cmd) {
+ Bundle data = new Bundle();
+ ActivityClientRecord r = mActivities.get(cmd.activityToken);
+ if (r != null) {
+ r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data);
+ r.activity.onProvideAssistData(data);
+ }
+ if (data.isEmpty()) {
+ data = null;
+ }
+ IActivityManager mgr = ActivityManagerNative.getDefault();
+ try {
+ mgr.reportTopActivityExtras(cmd.requestToken, data);
+ } catch (RemoteException e) {
+ }
+ }
+
private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>();
/**
@@ -4277,9 +4325,13 @@ public final class ActivityThread {
// Enable OpenGL tracing if required
if (data.enableOpenGlTrace) {
- GLUtils.enableTracing();
+ GLUtils.setTracingLevel(1);
}
+ // Allow application-generated systrace messages if we're debuggable.
+ boolean appTracingAllowed = (data.appInfo.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ Trace.setAppTracingAllowed(appTracingAllowed);
+
/**
* Initialize the default http proxy in this process for the reasons we set the time zone.
*/
@@ -4336,7 +4388,8 @@ public final class ActivityThread {
}
mInstrumentation.init(this, instrContext, appContext,
- new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher);
+ new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
+ data.instrumentationUiAutomationConnection);
if (mProfiler.profileFile != null && !ii.handleProfiling
&& mProfiler.profileFd == null) {
@@ -4901,7 +4954,7 @@ public final class ActivityThread {
}
private void attach(boolean system) {
- sThreadLocal.set(this);
+ sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
ViewRootImpl.addFirstDrawHandler(new Runnable() {
@@ -5027,6 +5080,8 @@ public final class ActivityThread {
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
+ Security.addProvider(new AndroidKeyStoreProvider());
+
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
diff --git a/core/java/android/app/AppOpsManager.aidl b/core/java/android/app/AppOpsManager.aidl
new file mode 100644
index 0000000..4b97a15
--- /dev/null
+++ b/core/java/android/app/AppOpsManager.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+parcelable AppOpsManager.PackageOps;
+parcelable AppOpsManager.OpEntry;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
new file mode 100644
index 0000000..4fcb18a
--- /dev/null
+++ b/core/java/android/app/AppOpsManager.java
@@ -0,0 +1,541 @@
+/*
+ * 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.Manifest;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IAppOpsCallback;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+
+/**
+ * API for interacting with "application operation" tracking. Allows you to:
+ *
+ * - Note when operations are happening, and find out if they are allowed for the current caller.
+ * - Disallow specific apps from doing specific operations.
+ * - Collect all of the current information about operations that have been executed or are not
+ * being allowed.
+ * - Monitor for changes in whether an operation is allowed.
+ *
+ * Each operation is identified by a single integer; these integers are a fixed set of
+ * operations, enumerated by the OP_* constants.
+ *
+ * When checking operations, the result is a "mode" integer indicating the current setting
+ * for the operation under that caller: MODE_ALLOWED, MODE_IGNORED (don't execute the operation but
+ * fake its behavior enough so that the caller doesn't crash), MODE_ERRORED (through a
+ * SecurityException back to the caller; the normal operation calls will do this for you).
+ *
+ * @hide
+ */
+public class AppOpsManager {
+ final Context mContext;
+ final IAppOpsService mService;
+ final HashMap<Callback, IAppOpsCallback> mModeWatchers
+ = new HashMap<Callback, IAppOpsCallback>();
+
+ public static final int MODE_ALLOWED = 0;
+ public static final int MODE_IGNORED = 1;
+ public static final int MODE_ERRORED = 2;
+
+ // when adding one of these:
+ // - increment _NUM_OP
+ // - add rows to sOpToSwitch, sOpNames, sOpPerms
+ // - add descriptive strings to Settings/res/values/arrays.xml
+ public static final int OP_NONE = -1;
+ public static final int OP_COARSE_LOCATION = 0;
+ public static final int OP_FINE_LOCATION = 1;
+ public static final int OP_GPS = 2;
+ public static final int OP_VIBRATE = 3;
+ public static final int OP_READ_CONTACTS = 4;
+ public static final int OP_WRITE_CONTACTS = 5;
+ public static final int OP_READ_CALL_LOG = 6;
+ public static final int OP_WRITE_CALL_LOG = 7;
+ public static final int OP_READ_CALENDAR = 8;
+ public static final int OP_WRITE_CALENDAR = 9;
+ public static final int OP_WIFI_SCAN = 10;
+ public static final int OP_POST_NOTIFICATION = 11;
+ public static final int OP_NEIGHBORING_CELLS = 12;
+ public static final int OP_CALL_PHONE = 13;
+ public static final int OP_READ_SMS = 14;
+ public static final int OP_WRITE_SMS = 15;
+ public static final int OP_RECEIVE_SMS = 16;
+ public static final int OP_RECEIVE_EMERGECY_SMS = 17;
+ public static final int OP_RECEIVE_MMS = 18;
+ public static final int OP_RECEIVE_WAP_PUSH = 19;
+ public static final int OP_SEND_SMS = 20;
+ public static final int OP_READ_ICC_SMS = 21;
+ public static final int OP_WRITE_ICC_SMS = 22;
+ public static final int OP_WRITE_SETTINGS = 23;
+ public static final int OP_SYSTEM_ALERT_WINDOW = 24;
+ public static final int OP_ACCESS_NOTIFICATIONS = 25;
+ public static final int OP_CAMERA = 26;
+ public static final int OP_RECORD_AUDIO = 27;
+ public static final int OP_PLAY_AUDIO = 28;
+ public static final int OP_READ_CLIPBOARD = 29;
+ public static final int OP_WRITE_CLIPBOARD = 30;
+ /** @hide */
+ public static final int _NUM_OP = 31;
+
+ /**
+ * This maps each operation to the operation that serves as the
+ * switch to determine whether it is allowed. Generally this is
+ * a 1:1 mapping, but for some things (like location) that have
+ * multiple low-level operations being tracked that should be
+ * presented to hte user as one switch then this can be used to
+ * make them all controlled by the same single operation.
+ */
+ private static int[] sOpToSwitch = new int[] {
+ OP_COARSE_LOCATION,
+ OP_COARSE_LOCATION,
+ OP_COARSE_LOCATION,
+ OP_VIBRATE,
+ OP_READ_CONTACTS,
+ OP_WRITE_CONTACTS,
+ OP_READ_CALL_LOG,
+ OP_WRITE_CALL_LOG,
+ OP_READ_CALENDAR,
+ OP_WRITE_CALENDAR,
+ OP_COARSE_LOCATION,
+ OP_POST_NOTIFICATION,
+ OP_COARSE_LOCATION,
+ OP_CALL_PHONE,
+ OP_READ_SMS,
+ OP_WRITE_SMS,
+ OP_READ_SMS,
+ OP_READ_SMS,
+ OP_READ_SMS,
+ OP_READ_SMS,
+ OP_WRITE_SMS,
+ OP_READ_SMS,
+ OP_WRITE_SMS,
+ OP_WRITE_SETTINGS,
+ OP_SYSTEM_ALERT_WINDOW,
+ OP_ACCESS_NOTIFICATIONS,
+ OP_CAMERA,
+ OP_RECORD_AUDIO,
+ OP_PLAY_AUDIO,
+ OP_READ_CLIPBOARD,
+ OP_WRITE_CLIPBOARD,
+ };
+
+ /**
+ * This provides a simple name for each operation to be used
+ * in debug output.
+ */
+ private static String[] sOpNames = new String[] {
+ "COARSE_LOCATION",
+ "FINE_LOCATION",
+ "GPS",
+ "VIBRATE",
+ "READ_CONTACTS",
+ "WRITE_CONTACTS",
+ "READ_CALL_LOG",
+ "WRITE_CALL_LOG",
+ "READ_CALENDAR",
+ "WRITE_CALENDAR",
+ "WIFI_SCAN",
+ "POST_NOTIFICATION",
+ "NEIGHBORING_CELLS",
+ "CALL_PHONE",
+ "READ_SMS",
+ "WRITE_SMS",
+ "RECEIVE_SMS",
+ "RECEIVE_EMERGECY_SMS",
+ "RECEIVE_MMS",
+ "RECEIVE_WAP_PUSH",
+ "SEND_SMS",
+ "READ_ICC_SMS",
+ "WRITE_ICC_SMS",
+ "WRITE_SETTINGS",
+ "SYSTEM_ALERT_WINDOW",
+ "ACCESS_NOTIFICATIONS",
+ "CAMERA",
+ "RECORD_AUDIO",
+ "PLAY_AUDIO",
+ "READ_CLIPBOARD",
+ "WRITE_CLIPBOARD",
+ };
+
+ /**
+ * This optionally maps a permission to an operation. If there
+ * is no permission associated with an operation, it is null.
+ */
+ private static String[] sOpPerms = new String[] {
+ android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ null,
+ android.Manifest.permission.VIBRATE,
+ android.Manifest.permission.READ_CONTACTS,
+ android.Manifest.permission.WRITE_CONTACTS,
+ android.Manifest.permission.READ_CALL_LOG,
+ android.Manifest.permission.WRITE_CALL_LOG,
+ android.Manifest.permission.READ_CALENDAR,
+ android.Manifest.permission.WRITE_CALENDAR,
+ null, // no permission required for notifications
+ android.Manifest.permission.ACCESS_WIFI_STATE,
+ null, // neighboring cells shares the coarse location perm
+ android.Manifest.permission.CALL_PHONE,
+ android.Manifest.permission.READ_SMS,
+ android.Manifest.permission.WRITE_SMS,
+ android.Manifest.permission.RECEIVE_SMS,
+ android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
+ android.Manifest.permission.RECEIVE_MMS,
+ android.Manifest.permission.RECEIVE_WAP_PUSH,
+ android.Manifest.permission.SEND_SMS,
+ android.Manifest.permission.READ_SMS,
+ android.Manifest.permission.WRITE_SMS,
+ android.Manifest.permission.WRITE_SETTINGS,
+ android.Manifest.permission.SYSTEM_ALERT_WINDOW,
+ android.Manifest.permission.ACCESS_NOTIFICATIONS,
+ android.Manifest.permission.CAMERA,
+ android.Manifest.permission.RECORD_AUDIO,
+ null, // no permission for playing audio
+ null, // no permission for reading clipboard
+ null, // no permission for writing clipboard
+ };
+
+ /**
+ * Retrieve the op switch that controls the given operation.
+ */
+ public static int opToSwitch(int op) {
+ return sOpToSwitch[op];
+ }
+
+ /**
+ * Retrieve a non-localized name for the operation, for debugging output.
+ */
+ public static String opToName(int op) {
+ if (op == OP_NONE) return "NONE";
+ return op < sOpNames.length ? sOpNames[op] : ("Unknown(" + op + ")");
+ }
+
+ /**
+ * Retrieve the permission associated with an operation, or null if there is not one.
+ */
+ public static String opToPermission(int op) {
+ return sOpPerms[op];
+ }
+
+ /**
+ * Class holding all of the operation information associated with an app.
+ */
+ public static class PackageOps implements Parcelable {
+ private final String mPackageName;
+ private final int mUid;
+ private final List<OpEntry> mEntries;
+
+ public PackageOps(String packageName, int uid, List<OpEntry> entries) {
+ mPackageName = packageName;
+ mUid = uid;
+ mEntries = entries;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public List<OpEntry> getOps() {
+ return mEntries;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mPackageName);
+ dest.writeInt(mUid);
+ dest.writeInt(mEntries.size());
+ for (int i=0; i<mEntries.size(); i++) {
+ mEntries.get(i).writeToParcel(dest, flags);
+ }
+ }
+
+ PackageOps(Parcel source) {
+ mPackageName = source.readString();
+ mUid = source.readInt();
+ mEntries = new ArrayList<OpEntry>();
+ final int N = source.readInt();
+ for (int i=0; i<N; i++) {
+ mEntries.add(OpEntry.CREATOR.createFromParcel(source));
+ }
+ }
+
+ public static final Creator<PackageOps> CREATOR = new Creator<PackageOps>() {
+ @Override public PackageOps createFromParcel(Parcel source) {
+ return new PackageOps(source);
+ }
+
+ @Override public PackageOps[] newArray(int size) {
+ return new PackageOps[size];
+ }
+ };
+ }
+
+ /**
+ * Class holding the information about one unique operation of an application.
+ */
+ public static class OpEntry implements Parcelable {
+ private final int mOp;
+ private final int mMode;
+ private final long mTime;
+ private final long mRejectTime;
+ private final int mDuration;
+
+ public OpEntry(int op, int mode, long time, long rejectTime, int duration) {
+ mOp = op;
+ mMode = mode;
+ mTime = time;
+ mRejectTime = rejectTime;
+ mDuration = duration;
+ }
+
+ public int getOp() {
+ return mOp;
+ }
+
+ public int getMode() {
+ return mMode;
+ }
+
+ public long getTime() {
+ return mTime;
+ }
+
+ public long getRejectTime() {
+ return mRejectTime;
+ }
+
+ public boolean isRunning() {
+ return mDuration == -1;
+ }
+
+ public int getDuration() {
+ return mDuration == -1 ? (int)(System.currentTimeMillis()-mTime) : mDuration;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mOp);
+ dest.writeInt(mMode);
+ dest.writeLong(mTime);
+ dest.writeLong(mRejectTime);
+ dest.writeInt(mDuration);
+ }
+
+ OpEntry(Parcel source) {
+ mOp = source.readInt();
+ mMode = source.readInt();
+ mTime = source.readLong();
+ mRejectTime = source.readLong();
+ mDuration = source.readInt();
+ }
+
+ public static final Creator<OpEntry> CREATOR = new Creator<OpEntry>() {
+ @Override public OpEntry createFromParcel(Parcel source) {
+ return new OpEntry(source);
+ }
+
+ @Override public OpEntry[] newArray(int size) {
+ return new OpEntry[size];
+ }
+ };
+ }
+
+ /**
+ * Callback for notification of changes to operation state.
+ */
+ public interface Callback {
+ public void opChanged(int op, String packageName);
+ }
+
+ public AppOpsManager(Context context, IAppOpsService service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Retrieve current operation state for all applications.
+ *
+ * @param ops The set of operations you are interested in, or null if you want all of them.
+ */
+ public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
+ try {
+ return mService.getPackagesForOps(ops);
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve current operation state for one application.
+ *
+ * @param uid The uid of the application of interest.
+ * @param packageName The name of the application of interest.
+ * @param ops The set of operations you are interested in, or null if you want all of them.
+ */
+ public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) {
+ try {
+ return mService.getOpsForPackage(uid, packageName, ops);
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ public void setMode(int code, int uid, String packageName, int mode) {
+ try {
+ mService.setMode(code, uid, packageName, mode);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /** @hide */
+ public void resetAllModes() {
+ try {
+ mService.resetAllModes();
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void startWatchingMode(int op, String packageName, final Callback callback) {
+ synchronized (mModeWatchers) {
+ IAppOpsCallback cb = mModeWatchers.get(callback);
+ if (cb == null) {
+ cb = new IAppOpsCallback.Stub() {
+ public void opChanged(int op, String packageName) {
+ callback.opChanged(op, packageName);
+ }
+ };
+ mModeWatchers.put(callback, cb);
+ }
+ try {
+ mService.startWatchingMode(op, packageName, cb);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ public void stopWatchingMode(Callback callback) {
+ synchronized (mModeWatchers) {
+ IAppOpsCallback cb = mModeWatchers.get(callback);
+ if (cb != null) {
+ try {
+ mService.stopWatchingMode(cb);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ public int checkOp(int op, int uid, String packageName) {
+ try {
+ int mode = mService.checkOperation(op, uid, packageName);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException("Operation not allowed");
+ }
+ return mode;
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ public int checkOpNoThrow(int op, int uid, String packageName) {
+ try {
+ return mService.checkOperation(op, uid, packageName);
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ public int noteOp(int op, int uid, String packageName) {
+ try {
+ int mode = mService.noteOperation(op, uid, packageName);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException("Operation not allowed");
+ }
+ return mode;
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ public int noteOpNoThrow(int op, int uid, String packageName) {
+ try {
+ return mService.noteOperation(op, uid, packageName);
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ public int noteOp(int op) {
+ return noteOp(op, Process.myUid(), mContext.getBasePackageName());
+ }
+
+ public int startOp(int op, int uid, String packageName) {
+ try {
+ int mode = mService.startOperation(op, uid, packageName);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException("Operation not allowed");
+ }
+ return mode;
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ public int startOpNoThrow(int op, int uid, String packageName) {
+ try {
+ return mService.startOperation(op, uid, packageName);
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ public int startOp(int op) {
+ return startOp(op, Process.myUid(), mContext.getBasePackageName());
+ }
+
+ public void finishOp(int op, int uid, String packageName) {
+ try {
+ mService.finishOperation(op, uid, packageName);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void finishOp(int op) {
+ finishOp(op, Process.myUid(), mContext.getBasePackageName());
+ }
+}
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 3a67cec..75e4bab 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -22,6 +22,7 @@ import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
@@ -45,6 +46,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
new ArrayList<ComponentCallbacks>();
private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
new ArrayList<ActivityLifecycleCallbacks>();
+ private ArrayList<OnProvideAssistDataListener> mAssistCallbacks = null;
/** @hide */
public LoadedApk mLoadedApk;
@@ -59,6 +61,21 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
void onActivityDestroyed(Activity activity);
}
+ /**
+ * Callback interface for use with {@link Application#registerOnProvideAssistDataListener}
+ * and {@link Application#unregisterOnProvideAssistDataListener}.
+ */
+ public interface OnProvideAssistDataListener {
+ /**
+ * This is called when the user is requesting an assist, to build a full
+ * {@link Intent#ACTION_ASSIST} Intent with all of the context of the current
+ * application. You can override this method to place into the bundle anything
+ * you would like to appear in the {@link Intent#EXTRA_ASSIST_CONTEXT} part
+ * of the assist Intent.
+ */
+ public void onProvideAssistData(Activity activity, Bundle data);
+ }
+
public Application() {
super(null);
}
@@ -137,7 +154,24 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
mActivityLifecycleCallbacks.remove(callback);
}
}
-
+
+ public void registerOnProvideAssistDataListener(OnProvideAssistDataListener callback) {
+ synchronized (this) {
+ if (mAssistCallbacks == null) {
+ mAssistCallbacks = new ArrayList<OnProvideAssistDataListener>();
+ }
+ mAssistCallbacks.add(callback);
+ }
+ }
+
+ public void unregisterOnProvideAssistDataListener(OnProvideAssistDataListener callback) {
+ synchronized (this) {
+ if (mAssistCallbacks != null) {
+ mAssistCallbacks.remove(callback);
+ }
+ }
+ }
+
// ------------------ Internal API ------------------
/**
@@ -232,4 +266,19 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
return callbacks;
}
+
+ /* package */ void dispatchOnProvideAssistData(Activity activity, Bundle data) {
+ Object[] callbacks;
+ synchronized (this) {
+ if (mAssistCallbacks == null) {
+ return;
+ }
+ callbacks = mAssistCallbacks.toArray();
+ }
+ if (callbacks != null) {
+ for (int i=0; i<callbacks.length; i++) {
+ ((OnProvideAssistDataListener)callbacks[i]).onProvideAssistData(activity, data);
+ }
+ }
+ }
}
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index 9e3cd7e..a26b88c 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -16,6 +16,7 @@
package android.app;
+import android.os.Trace;
import dalvik.system.PathClassLoader;
import java.util.HashMap;
@@ -54,14 +55,19 @@ class ApplicationLoaders
return loader;
}
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
PathClassLoader pathClassloader =
new PathClassLoader(zip, libPath, parent);
-
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
mLoaders.put(zip, pathClassloader);
return pathClassloader;
}
- return new PathClassLoader(zip, parent);
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
+ PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ return pathClassloader;
}
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7431765..271494f 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -50,6 +50,7 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Log;
import android.view.Display;
@@ -142,6 +143,21 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
+ public int getPackageUid(String packageName, int userHandle)
+ throws NameNotFoundException {
+ try {
+ int uid = mPM.getPackageUid(packageName, userHandle);
+ if (uid >= 0) {
+ return uid;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+
+ throw new NameNotFoundException(packageName);
+ }
+
+ @Override
public PermissionInfo getPermissionInfo(String name, int flags)
throws NameNotFoundException {
try {
@@ -411,17 +427,22 @@ final class ApplicationPackageManager extends PackageManager {
@Override
public List<PackageInfo> getInstalledPackages(int flags, int userId) {
try {
- final List<PackageInfo> packageInfos = new ArrayList<PackageInfo>();
- PackageInfo lastItem = null;
- ParceledListSlice<PackageInfo> slice;
-
- do {
- final String lastKey = lastItem != null ? lastItem.packageName : null;
- slice = mPM.getInstalledPackages(flags, lastKey, userId);
- lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR);
- } while (!slice.isLastSlice());
+ ParceledListSlice<PackageInfo> slice = mPM.getInstalledPackages(flags, userId);
+ return slice.getList();
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ }
- return packageInfos;
+ @SuppressWarnings("unchecked")
+ @Override
+ public List<PackageInfo> getPackagesHoldingPermissions(
+ String[] permissions, int flags) {
+ final int userId = mContext.getUserId();
+ try {
+ ParceledListSlice<PackageInfo> slice = mPM.getPackagesHoldingPermissions(
+ permissions, flags, userId);
+ return slice.getList();
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
@@ -432,17 +453,8 @@ final class ApplicationPackageManager extends PackageManager {
public List<ApplicationInfo> getInstalledApplications(int flags) {
final int userId = mContext.getUserId();
try {
- final List<ApplicationInfo> applicationInfos = new ArrayList<ApplicationInfo>();
- ApplicationInfo lastItem = null;
- ParceledListSlice<ApplicationInfo> slice;
-
- do {
- final String lastKey = lastItem != null ? lastItem.packageName : null;
- slice = mPM.getInstalledApplications(flags, lastKey, userId);
- lastItem = slice.populateList(applicationInfos, ApplicationInfo.CREATOR);
- } while (!slice.isLastSlice());
-
- return applicationInfos;
+ ParceledListSlice<ApplicationInfo> slice = mPM.getInstalledApplications(flags, userId);
+ return slice.getList();
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
@@ -1053,7 +1065,7 @@ final class ApplicationPackageManager extends PackageManager {
public int installExistingPackage(String packageName)
throws NameNotFoundException {
try {
- int res = mPM.installExistingPackage(packageName);
+ int res = mPM.installExistingPackageAsUser(packageName, UserHandle.myUserId());
if (res == INSTALL_FAILED_INVALID_URI) {
throw new NameNotFoundException("Package " + packageName + " doesn't exist");
}
@@ -1115,7 +1127,7 @@ final class ApplicationPackageManager extends PackageManager {
@Override
public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {
try {
- mPM.deletePackage(packageName, observer, flags);
+ mPM.deletePackageAsUser(packageName, observer, UserHandle.myUserId(), flags);
} catch (RemoteException e) {
// Should never happen!
}
@@ -1267,7 +1279,8 @@ final class ApplicationPackageManager extends PackageManager {
public void setApplicationEnabledSetting(String packageName,
int newState, int flags) {
try {
- mPM.setApplicationEnabledSetting(packageName, newState, flags, mContext.getUserId());
+ mPM.setApplicationEnabledSetting(packageName, newState, flags,
+ mContext.getUserId(), mContext.getBasePackageName());
} catch (RemoteException e) {
// Should never happen!
}
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 63aa5f9..b1c58f2 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -267,6 +267,9 @@ public abstract class ApplicationThreadNative extends Binder
Bundle testArgs = data.readBundle();
IBinder binder = data.readStrongBinder();
IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder);
+ binder = data.readStrongBinder();
+ IUiAutomationConnection uiAutomationConnection =
+ IUiAutomationConnection.Stub.asInterface(binder);
int testMode = data.readInt();
boolean openGlTrace = data.readInt() != 0;
boolean restrictedBackupMode = (data.readInt() != 0);
@@ -277,8 +280,9 @@ public abstract class ApplicationThreadNative extends Binder
Bundle coreSettings = data.readBundle();
bindApplication(packageName, info,
providers, testName, profileName, profileFd, autoStopProfiler,
- testArgs, testWatcher, testMode, openGlTrace, restrictedBackupMode,
- persistent, config, compatInfo, services, coreSettings);
+ testArgs, testWatcher, uiAutomationConnection, testMode,
+ openGlTrace, restrictedBackupMode, persistent, config, compatInfo,
+ services, coreSettings);
return true;
}
@@ -587,6 +591,17 @@ public abstract class ApplicationThreadNative extends Binder
reply.writeNoException();
return true;
}
+
+ case REQUEST_ACTIVITY_EXTRAS_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ IBinder activityToken = data.readStrongBinder();
+ IBinder requestToken = data.readStrongBinder();
+ int requestType = data.readInt();
+ requestActivityExtras(activityToken, requestToken, requestType);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -863,10 +878,11 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void bindApplication(String packageName, ApplicationInfo info,
List<ProviderInfo> providers, ComponentName testName, String profileName,
ParcelFileDescriptor profileFd, boolean autoStopProfiler, Bundle testArgs,
- IInstrumentationWatcher testWatcher, int debugMode, boolean openGlTrace,
- boolean restrictedBackupMode, boolean persistent,
- Configuration config, CompatibilityInfo compatInfo,
- Map<String, IBinder> services, Bundle coreSettings) throws RemoteException {
+ IInstrumentationWatcher testWatcher,
+ IUiAutomationConnection uiAutomationConnection, int debugMode,
+ boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
+ Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
+ Bundle coreSettings) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeString(packageName);
@@ -888,6 +904,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeInt(autoStopProfiler ? 1 : 0);
data.writeBundle(testArgs);
data.writeStrongInterface(testWatcher);
+ data.writeStrongInterface(uiAutomationConnection);
data.writeInt(debugMode);
data.writeInt(openGlTrace ? 1 : 0);
data.writeInt(restrictedBackupMode ? 1 : 0);
@@ -1185,4 +1202,15 @@ class ApplicationThreadProxy implements IApplicationThread {
mRemote.transact(UNSTABLE_PROVIDER_DIED_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
data.recycle();
}
+
+ public void requestActivityExtras(IBinder activityToken, IBinder requestToken, int requestType)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ data.writeStrongBinder(activityToken);
+ data.writeStrongBinder(requestToken);
+ data.writeInt(requestType);
+ mRemote.transact(REQUEST_ACTIVITY_EXTRAS_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
+ data.recycle();
+ }
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f895ccc..3fc82fa 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -19,7 +19,7 @@ package android.app;
import com.android.internal.policy.PolicyManager;
import com.android.internal.util.Preconditions;
-import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -47,11 +47,9 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
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;
import android.hardware.usb.UsbManager;
@@ -65,8 +63,6 @@ import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.INetworkPolicyManager;
import android.net.NetworkPolicyManager;
-import android.net.ThrottleManager;
-import android.net.IThrottleManager;
import android.net.Uri;
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
@@ -109,6 +105,8 @@ import android.view.textservice.TextServicesManager;
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
import android.app.admin.DevicePolicyManager;
+
+import com.android.internal.app.IAppOpsService;
import com.android.internal.os.IDropBoxManagerService;
import java.io.File;
@@ -321,7 +319,7 @@ class ContextImpl extends Context {
registerService(BLUETOOTH_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
- return BluetoothAdapter.getDefaultAdapter();
+ return new BluetoothManager(ctx);
}});
registerService(CLIPBOARD_SERVICE, new ServiceFetcher() {
@@ -373,9 +371,9 @@ class ContextImpl extends Context {
return new DisplayManager(ctx.getOuterContext());
}});
- registerService(INPUT_METHOD_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return InputMethodManager.getInstance(ctx);
+ registerService(INPUT_METHOD_SERVICE, new StaticServiceFetcher() {
+ public Object createStaticService() {
+ return InputMethodManager.getInstance();
}});
registerService(TEXT_SERVICES_MANAGER_SERVICE, new ServiceFetcher() {
@@ -451,7 +449,8 @@ class ContextImpl extends Context {
registerService(SENSOR_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
- return new SystemSensorManager(ctx.mMainThread.getHandler().getLooper());
+ return new SystemSensorManager(ctx.getOuterContext(),
+ ctx.mMainThread.getHandler().getLooper());
}});
registerService(STATUS_BAR_SERVICE, new ServiceFetcher() {
@@ -462,7 +461,8 @@ class ContextImpl extends Context {
registerService(STORAGE_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
try {
- return new StorageManager(ctx.mMainThread.getHandler().getLooper());
+ return new StorageManager(
+ ctx.getContentResolver(), ctx.mMainThread.getHandler().getLooper());
} catch (RemoteException rex) {
Log.e(TAG, "Failed to create StorageManager", rex);
return null;
@@ -474,12 +474,6 @@ class ContextImpl extends Context {
return new TelephonyManager(ctx.getOuterContext());
}});
- registerService(THROTTLE_SERVICE, new StaticServiceFetcher() {
- public Object createStaticService() {
- IBinder b = ServiceManager.getService(THROTTLE_SERVICE);
- return new ThrottleManager(IThrottleManager.Stub.asInterface(b));
- }});
-
registerService(UI_MODE_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return new UiModeManager();
@@ -499,7 +493,7 @@ class ContextImpl extends Context {
registerService(VIBRATOR_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
- return new SystemVibrator();
+ return new SystemVibrator(ctx);
}});
registerService(WALLPAPER_SERVICE, WALLPAPER_FETCHER);
@@ -530,11 +524,18 @@ class ContextImpl extends Context {
}});
registerService(USER_SERVICE, new ServiceFetcher() {
- public Object getService(ContextImpl ctx) {
+ public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(USER_SERVICE);
IUserManager service = IUserManager.Stub.asInterface(b);
return new UserManager(ctx, service);
}});
+
+ registerService(APP_OPS_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(APP_OPS_SERVICE);
+ IAppOpsService service = IAppOpsService.Stub.asInterface(b);
+ return new AppOpsManager(ctx, service);
+ }});
}
static ContextImpl getImpl(Context context) {
@@ -624,7 +625,15 @@ class ContextImpl extends Context {
if (mPackageInfo != null) {
return mPackageInfo.getPackageName();
}
- throw new RuntimeException("Not supported in system context");
+ // No mPackageInfo means this is a Context for the system itself,
+ // and this here is its name.
+ return "android";
+ }
+
+ /** @hide */
+ @Override
+ public String getBasePackageName() {
+ return mBasePackageName != null ? mBasePackageName : getPackageName();
}
@Override
@@ -956,7 +965,7 @@ class ContextImpl extends Context {
public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
try {
ActivityManagerNative.getDefault().startActivityAsUser(
- mMainThread.getApplicationThread(), intent,
+ mMainThread.getApplicationThread(), getBasePackageName(), intent,
intent.resolveTypeIfNeeded(getContentResolver()),
null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, null, options,
user.getIdentifier());
@@ -1012,7 +1021,8 @@ class ContextImpl extends Context {
try {
String resolvedType = null;
if (fillInIntent != null) {
- fillInIntent.setAllowFds(false);
+ fillInIntent.migrateExtraStreamToClipData();
+ fillInIntent.prepareToLeaveProcess();
resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver());
}
int result = ActivityManagerNative.getDefault()
@@ -1032,10 +1042,10 @@ class ContextImpl extends Context {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, null, false, false,
+ Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, false,
getUserId());
} catch (RemoteException e) {
}
@@ -1046,10 +1056,24 @@ class ContextImpl extends Context {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, receiverPermission, false, false,
+ Activity.RESULT_OK, null, null, receiverPermission, AppOpsManager.OP_NONE,
+ false, false, getUserId());
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
+ warnIfCallingFromSystemProcess();
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+ try {
+ intent.prepareToLeaveProcess();
+ ActivityManagerNative.getDefault().broadcastIntent(
+ mMainThread.getApplicationThread(), intent, resolvedType, null,
+ Activity.RESULT_OK, null, null, receiverPermission, appOp, false, false,
getUserId());
} catch (RemoteException e) {
}
@@ -1061,10 +1085,10 @@ class ContextImpl extends Context {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, receiverPermission, true, false,
+ Activity.RESULT_OK, null, null, receiverPermission, AppOpsManager.OP_NONE, true, false,
getUserId());
} catch (RemoteException e) {
}
@@ -1075,6 +1099,15 @@ class ContextImpl extends Context {
String receiverPermission, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
Bundle initialExtras) {
+ sendOrderedBroadcast(intent, receiverPermission, AppOpsManager.OP_NONE,
+ resultReceiver, scheduler, initialCode, initialData, initialExtras);
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent,
+ String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
warnIfCallingFromSystemProcess();
IIntentReceiver rd = null;
if (resultReceiver != null) {
@@ -1095,11 +1128,11 @@ class ContextImpl extends Context {
}
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
- initialCode, initialData, initialExtras, receiverPermission,
- true, false, getUserId());
+ initialCode, initialData, initialExtras, receiverPermission, appOp,
+ true, false, getUserId());
} catch (RemoteException e) {
}
}
@@ -1108,10 +1141,10 @@ class ContextImpl extends Context {
public void sendBroadcastAsUser(Intent intent, UserHandle user) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(),
- intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false,
- user.getIdentifier());
+ intent, resolvedType, null, Activity.RESULT_OK, null, null, null,
+ AppOpsManager.OP_NONE, false, false, user.getIdentifier());
} catch (RemoteException e) {
}
}
@@ -1121,10 +1154,10 @@ class ContextImpl extends Context {
String receiverPermission) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, receiverPermission, false, false,
+ Activity.RESULT_OK, null, null, receiverPermission, AppOpsManager.OP_NONE, false, false,
user.getIdentifier());
} catch (RemoteException e) {
}
@@ -1153,11 +1186,11 @@ class ContextImpl extends Context {
}
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
initialCode, initialData, initialExtras, receiverPermission,
- true, false, user.getIdentifier());
+ AppOpsManager.OP_NONE, true, false, user.getIdentifier());
} catch (RemoteException e) {
}
}
@@ -1167,10 +1200,10 @@ class ContextImpl extends Context {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, null, false, true,
+ Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, true,
getUserId());
} catch (RemoteException e) {
}
@@ -1201,11 +1234,11 @@ class ContextImpl extends Context {
}
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
initialCode, initialData, initialExtras, null,
- true, true, getUserId());
+ AppOpsManager.OP_NONE, true, true, getUserId());
} catch (RemoteException e) {
}
}
@@ -1218,7 +1251,7 @@ class ContextImpl extends Context {
intent.setDataAndType(intent.getData(), resolvedType);
}
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().unbroadcastIntent(
mMainThread.getApplicationThread(), intent, getUserId());
} catch (RemoteException e) {
@@ -1229,10 +1262,10 @@ class ContextImpl extends Context {
public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, null, false, true, user.getIdentifier());
+ Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, true, user.getIdentifier());
} catch (RemoteException e) {
}
}
@@ -1261,11 +1294,11 @@ class ContextImpl extends Context {
}
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
initialCode, initialData, initialExtras, null,
- true, true, user.getIdentifier());
+ AppOpsManager.OP_NONE, true, true, user.getIdentifier());
} catch (RemoteException e) {
}
}
@@ -1278,7 +1311,7 @@ class ContextImpl extends Context {
intent.setDataAndType(intent.getData(), resolvedType);
}
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
ActivityManagerNative.getDefault().unbroadcastIntent(
mMainThread.getApplicationThread(), intent, user.getIdentifier());
} catch (RemoteException e) {
@@ -1362,7 +1395,7 @@ class ContextImpl extends Context {
@Override
public ComponentName startServiceAsUser(Intent service, UserHandle user) {
try {
- service.setAllowFds(false);
+ service.prepareToLeaveProcess();
ComponentName cn = ActivityManagerNative.getDefault().startService(
mMainThread.getApplicationThread(), service,
service.resolveTypeIfNeeded(getContentResolver()), user.getIdentifier());
@@ -1386,7 +1419,7 @@ class ContextImpl extends Context {
@Override
public boolean stopServiceAsUser(Intent service, UserHandle user) {
try {
- service.setAllowFds(false);
+ service.prepareToLeaveProcess();
int res = ActivityManagerNative.getDefault().stopService(
mMainThread.getApplicationThread(), service,
service.resolveTypeIfNeeded(getContentResolver()), user.getIdentifier());
@@ -1404,12 +1437,13 @@ class ContextImpl extends Context {
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
warnIfCallingFromSystemProcess();
- return bindService(service, conn, flags, UserHandle.getUserId(Process.myUid()));
+ return bindServiceAsUser(service, conn, flags, Process.myUserHandle());
}
/** @hide */
@Override
- public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) {
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+ UserHandle user) {
IServiceConnection sd;
if (conn == null) {
throw new IllegalArgumentException("connection is null");
@@ -1427,11 +1461,11 @@ class ContextImpl extends Context {
< android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
flags |= BIND_WAIVE_PRIORITY;
}
- service.setAllowFds(false);
+ service.prepareToLeaveProcess();
int res = ActivityManagerNative.getDefault().bindService(
mMainThread.getApplicationThread(), getActivityToken(),
service, service.resolveTypeIfNeeded(getContentResolver()),
- sd, flags, userHandle);
+ sd, flags, user.getIdentifier());
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);
@@ -1467,7 +1501,7 @@ class ContextImpl extends Context {
arguments.setAllowFds(false);
}
return ActivityManagerNative.getDefault().startInstrumentation(
- className, profileFile, 0, arguments, null, getUserId());
+ className, profileFile, 0, arguments, null, null, getUserId());
} catch (RemoteException e) {
// System has crashed, nothing we can do.
}
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 32e40ee..165c3db 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -461,39 +461,63 @@ public class DownloadManager {
}
/**
- * Set the local destination for the downloaded file to a path within the application's
- * external files directory (as returned by {@link Context#getExternalFilesDir(String)}.
+ * Set the local destination for the downloaded file to a path within
+ * the application's external files directory (as returned by
+ * {@link Context#getExternalFilesDir(String)}.
* <p>
- * The downloaded file is not scanned by MediaScanner.
- * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
+ * The downloaded file is not scanned by MediaScanner. But it can be
+ * made scannable by calling {@link #allowScanningByMediaScanner()}.
*
- * @param context the {@link Context} to use in determining the external files directory
- * @param dirType the directory type to pass to {@link Context#getExternalFilesDir(String)}
- * @param subPath the path within the external directory, including the destination filename
+ * @param context the {@link Context} to use in determining the external
+ * files directory
+ * @param dirType the directory type to pass to
+ * {@link Context#getExternalFilesDir(String)}
+ * @param subPath the path within the external directory, including the
+ * destination filename
* @return this object
+ * @throws IllegalStateException If the external storage directory
+ * cannot be found or created.
*/
public Request setDestinationInExternalFilesDir(Context context, String dirType,
String subPath) {
- setDestinationFromBase(context.getExternalFilesDir(dirType), subPath);
+ final File file = context.getExternalFilesDir(dirType);
+ if (file == null) {
+ throw new IllegalStateException("Failed to get external storage files directory");
+ } else if (file.exists()) {
+ if (!file.isDirectory()) {
+ throw new IllegalStateException(file.getAbsolutePath() +
+ " already exists and is not a directory");
+ }
+ } else {
+ if (!file.mkdirs()) {
+ throw new IllegalStateException("Unable to create directory: "+
+ file.getAbsolutePath());
+ }
+ }
+ setDestinationFromBase(file, subPath);
return this;
}
/**
- * Set the local destination for the downloaded file to a path within the public external
- * storage directory (as returned by
- * {@link Environment#getExternalStoragePublicDirectory(String)}.
- *<p>
- * The downloaded file is not scanned by MediaScanner.
- * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
+ * Set the local destination for the downloaded file to a path within
+ * the public external storage directory (as returned by
+ * {@link Environment#getExternalStoragePublicDirectory(String)}).
+ * <p>
+ * The downloaded file is not scanned by MediaScanner. But it can be
+ * made scannable by calling {@link #allowScanningByMediaScanner()}.
*
- * @param dirType the directory type to pass to
- * {@link Environment#getExternalStoragePublicDirectory(String)}
- * @param subPath the path within the external directory, including the destination filename
+ * @param dirType the directory type to pass to {@link Environment#getExternalStoragePublicDirectory(String)}
+ * @param subPath the path within the external directory, including the
+ * destination filename
* @return this object
+ * @throws IllegalStateException If the external storage directory
+ * cannot be found or created.
*/
public Request setDestinationInExternalPublicDir(String dirType, String subPath) {
File file = Environment.getExternalStoragePublicDirectory(dirType);
- if (file.exists()) {
+ if (file == null) {
+ throw new IllegalStateException("Failed to get external storage public directory");
+ } else if (file.exists()) {
if (!file.isDirectory()) {
throw new IllegalStateException(file.getAbsolutePath() +
" already exists and is not a directory");
@@ -1085,6 +1109,7 @@ public class DownloadManager {
values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
values.putNull(Downloads.Impl._DATA);
values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
+ values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, 0);
mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
}
diff --git a/core/java/android/app/IActivityController.aidl b/core/java/android/app/IActivityController.aidl
index aca8305..952c900 100644
--- a/core/java/android/app/IActivityController.aidl
+++ b/core/java/android/app/IActivityController.aidl
@@ -58,4 +58,11 @@ interface IActivityController
* immediately.
*/
int appNotResponding(String processName, int pid, String processStats);
+
+ /**
+ * The system process watchdog has detected that the system seems to be
+ * hung. Return 1 to continue waiting, or -1 to let it continue with its
+ * normal kill.
+ */
+ int systemNotResponding(String msg);
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 8af17a4..a21caee 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -51,19 +51,19 @@ import java.util.List;
* {@hide}
*/
public interface IActivityManager extends IInterface {
- public int startActivity(IApplicationThread caller,
+ public int startActivity(IApplicationThread caller, String callingPackage,
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,
+ public int startActivityAsUser(IApplicationThread caller, String callingPackage,
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,
+ public WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int flags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;
- public int startActivityWithConfig(IApplicationThread caller,
+ public int startActivityWithConfig(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, Configuration newConfig,
Bundle options, int userId) throws RemoteException;
@@ -85,7 +85,7 @@ public interface IActivityManager extends IInterface {
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;
+ int appOp, boolean serialized, boolean sticky, int userId) throws RemoteException;
public void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) throws RemoteException;
public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException;
public void attachApplication(IApplicationThread app) throws RemoteException;
@@ -158,8 +158,8 @@ 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 userId)
- throws RemoteException;
+ int flags, Bundle arguments, IInstrumentationWatcher watcher,
+ IUiAutomationConnection connection, int userId) throws RemoteException;
public void finishInstrumentation(IApplicationThread target,
int resultCode, Bundle results) throws RemoteException;
@@ -285,7 +285,9 @@ public interface IActivityManager extends IInterface {
int enterAnim, int exitAnim) throws RemoteException;
public boolean isUserAMonkey() throws RemoteException;
-
+
+ public void setUserIsMonkey(boolean monkey) throws RemoteException;
+
public void finishHeavyWeightApp() throws RemoteException;
public void setImmersive(IBinder token, boolean immersive) throws RemoteException;
@@ -310,7 +312,7 @@ public interface IActivityManager extends IInterface {
public boolean dumpHeap(String process, int userId, boolean managed, String path,
ParcelFileDescriptor fd) throws RemoteException;
- public int startActivities(IApplicationThread caller,
+ public int startActivities(IApplicationThread caller, String callingPackage,
Intent[] intents, String[] resolvedTypes, IBinder resultTo,
Bundle options, int userId) throws RemoteException;
@@ -357,9 +359,10 @@ public interface IActivityManager extends IInterface {
public boolean navigateUpTo(IBinder token, Intent target, int resultCode, Intent resultData)
throws RemoteException;
- // This is not public because you need to be very careful in how you
+ // These are not public because you need to be very careful in how you
// manage your activity to make sure it is always the uid you expect.
public int getLaunchedFromUid(IBinder activityToken) throws RemoteException;
+ public String getLaunchedFromPackage(IBinder activityToken) throws RemoteException;
public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException;
public void unregisterUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException;
@@ -368,6 +371,14 @@ public interface IActivityManager extends IInterface {
public long inputDispatchingTimedOut(int pid, boolean aboveSystem) throws RemoteException;
+ public Bundle getTopActivityExtras(int requestType) throws RemoteException;
+
+ public void reportTopActivityExtras(IBinder token, Bundle extras) throws RemoteException;
+
+ public void killUid(int uid, String reason) throws RemoteException;
+
+ public void hang(IBinder who, boolean allowRestart) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -624,4 +635,10 @@ public interface IActivityManager extends IInterface {
int INPUT_DISPATCHING_TIMED_OUT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+158;
int CLEAR_PENDING_BACKUP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+159;
int GET_INTENT_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+160;
+ int GET_TOP_ACTIVITY_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+161;
+ int REPORT_TOP_ACTIVITY_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+162;
+ int GET_LAUNCHED_FROM_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+163;
+ int KILL_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+164;
+ int SET_USER_IS_MONKEY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+165;
+ int HANG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+166;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 03a26d4..3189b31 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -90,7 +90,8 @@ public interface IApplicationThread extends IInterface {
void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers,
ComponentName testName, String profileName, ParcelFileDescriptor profileFd,
boolean autoStopProfiler, Bundle testArguments, IInstrumentationWatcher testWatcher,
- int debugMode, boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
+ IUiAutomationConnection uiAutomationConnection, int debugMode,
+ boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
Bundle coreSettings) throws RemoteException;
void scheduleExit() throws RemoteException;
@@ -130,6 +131,8 @@ public interface IApplicationThread extends IInterface {
void dumpGfxInfo(FileDescriptor fd, String[] args) throws RemoteException;
void dumpDbInfo(FileDescriptor fd, String[] args) throws RemoteException;
void unstableProviderDied(IBinder provider) throws RemoteException;
+ void requestActivityExtras(IBinder activityToken, IBinder requestToken, int requestType)
+ throws RemoteException;
String descriptor = "android.app.IApplicationThread";
@@ -179,4 +182,5 @@ public interface IApplicationThread extends IInterface {
int DUMP_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+44;
int DUMP_DB_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+45;
int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+46;
+ int REQUEST_ACTIVITY_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+47;
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 62d4962..9f933ca 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -18,8 +18,11 @@
package android.app;
import android.app.ITransientNotification;
+import android.service.notification.StatusBarNotification;
import android.app.Notification;
+import android.content.ComponentName;
import android.content.Intent;
+import android.service.notification.INotificationListener;
/** {@hide} */
interface INotificationManager
@@ -28,11 +31,21 @@ interface INotificationManager
void enqueueToast(String pkg, ITransientNotification callback, int duration);
void cancelToast(String pkg, ITransientNotification callback);
- void enqueueNotificationWithTag(String pkg, String tag, int id,
+ void enqueueNotificationWithTag(String pkg, String basePkg, 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);
-}
+ void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
+ boolean areNotificationsEnabledForPackage(String pkg, int uid);
+ StatusBarNotification[] getActiveNotifications(String callingPkg);
+ StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);
+
+ void registerListener(in INotificationListener listener, in ComponentName component, int userid);
+ void unregisterListener(in INotificationListener listener, int userid);
+
+ void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id);
+ void cancelAllNotificationsFromListener(in INotificationListener token);
+
+ StatusBarNotification[] getActiveNotificationsFromListener(in INotificationListener token);
+} \ No newline at end of file
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
new file mode 100644
index 0000000..09bf829
--- /dev/null
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.graphics.Bitmap;
+import android.view.InputEvent;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * This interface contains privileged operations a shell program can perform
+ * on behalf of an instrumentation that it runs. These operations require
+ * special permissions which the shell user has but the instrumentation does
+ * not. Running privileged operations by the shell user on behalf of an
+ * instrumentation is needed for running UiTestCases.
+ *
+ * {@hide}
+ */
+interface IUiAutomationConnection {
+ void connect(IAccessibilityServiceClient client);
+ void disconnect();
+ boolean injectInputEvent(in InputEvent event, boolean sync);
+ boolean setRotation(int rotation);
+ Bitmap takeScreenshot(int width, int height);
+ void shutdown();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index e0856ae..a307a73 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -27,6 +27,7 @@ import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
+import android.os.Looper;
import android.os.MessageQueue;
import android.os.PerformanceCollector;
import android.os.Process;
@@ -48,7 +49,6 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
-
/**
* Base class for implementing application instrumentation code. When running
* with instrumentation turned on, this class will be instantiated for you
@@ -58,6 +58,7 @@ import java.util.List;
* &lt;instrumentation&gt; tag.
*/
public class Instrumentation {
+
/**
* If included in the status or final bundle sent to an IInstrumentationWatcher, this key
* identifies the class that is writing the report. This can be used to provide more structured
@@ -72,7 +73,7 @@ public class Instrumentation {
* instrumentation can also be launched, and results collected, by an automated system.
*/
public static final String REPORT_KEY_STREAMRESULT = "stream";
-
+
private static final String TAG = "Instrumentation";
private final Object mSync = new Object();
@@ -85,9 +86,11 @@ public class Instrumentation {
private List<ActivityWaiter> mWaitingActivities;
private List<ActivityMonitor> mActivityMonitors;
private IInstrumentationWatcher mWatcher;
+ private IUiAutomationConnection mUiAutomationConnection;
private boolean mAutomaticPerformanceSnapshots = false;
private PerformanceCollector mPerformanceCollector;
private Bundle mPerfMetrics = new Bundle();
+ private UiAutomation mUiAutomation;
public Instrumentation() {
}
@@ -186,6 +189,10 @@ public class Instrumentation {
if (mPerfMetrics != null) {
results.putAll(mPerfMetrics);
}
+ if (mUiAutomation != null) {
+ mUiAutomation.disconnect();
+ mUiAutomation = null;
+ }
mThread.finishInstrumentation(resultCode, results);
}
@@ -1407,10 +1414,10 @@ public class Instrumentation {
}
}
try {
- intent.setAllowFds(false);
intent.migrateExtraStreamToClipData();
+ intent.prepareToLeaveProcess();
int result = ActivityManagerNative.getDefault()
- .startActivity(whoThread, intent,
+ .startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options);
@@ -1464,19 +1471,21 @@ public class Instrumentation {
try {
String[] resolvedTypes = new String[intents.length];
for (int i=0; i<intents.length; i++) {
- intents[i].setAllowFds(false);
+ intents[i].migrateExtraStreamToClipData();
+ intents[i].prepareToLeaveProcess();
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver());
}
int result = ActivityManagerNative.getDefault()
- .startActivities(whoThread, intents, resolvedTypes, token, options,
- userId);
+ .startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes,
+ token, options, userId);
checkStartActivityResult(result, intents[0]);
} catch (RemoteException e) {
}
}
/**
- * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+ * Like {@link #execStartActivity(android.content.Context, android.os.IBinder,
+ * android.os.IBinder, Fragment, android.content.Intent, int, android.os.Bundle)},
* but for calls from a {#link Fragment}.
*
* @param who The Context from which the activity is being started.
@@ -1522,10 +1531,10 @@ public class Instrumentation {
}
}
try {
- intent.setAllowFds(false);
intent.migrateExtraStreamToClipData();
+ intent.prepareToLeaveProcess();
int result = ActivityManagerNative.getDefault()
- .startActivity(whoThread, intent,
+ .startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mWho : null,
requestCode, 0, null, null, options);
@@ -1582,10 +1591,10 @@ public class Instrumentation {
}
}
try {
- intent.setAllowFds(false);
intent.migrateExtraStreamToClipData();
+ intent.prepareToLeaveProcess();
int result = ActivityManagerNative.getDefault()
- .startActivityAsUser(whoThread, intent,
+ .startActivityAsUser(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options, user.getIdentifier());
@@ -1597,13 +1606,14 @@ public class Instrumentation {
/*package*/ final void init(ActivityThread thread,
Context instrContext, Context appContext, ComponentName component,
- IInstrumentationWatcher watcher) {
+ IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection) {
mThread = thread;
mMessageQueue = mThread.getLooper().myQueue();
mInstrContext = instrContext;
mAppContext = appContext;
mComponent = component;
mWatcher = watcher;
+ mUiAutomationConnection = uiAutomationConnection;
}
/*package*/ static void checkStartActivityResult(int res, Object intent) {
@@ -1637,18 +1647,48 @@ public class Instrumentation {
}
private final void validateNotAppThread() {
- if (ActivityThread.currentActivityThread() != null) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException(
"This method can not be called from the main application thread");
}
}
+ /**
+ * Gets the {@link UiAutomation} instance.
+ * <p>
+ * <strong>Note:</strong> The APIs exposed via the returned {@link UiAutomation}
+ * work across application boundaries while the APIs exposed by the instrumentation
+ * do not. For example, {@link Instrumentation#sendPointerSync(MotionEvent)} will
+ * not allow you to inject the event in an app different from the instrumentation
+ * target, while {@link UiAutomation#injectInputEvent(android.view.InputEvent, boolean)}
+ * will work regardless of the current application.
+ * </p>
+ * <p>
+ * A typical test case should be using either the {@link UiAutomation} or
+ * {@link Instrumentation} APIs. Using both APIs at the same time is not
+ * a mistake by itself but a client has to be aware of the APIs limitations.
+ * </p>
+ * @return The UI automation instance.
+ *
+ * @see UiAutomation
+ */
+ public UiAutomation getUiAutomation() {
+ if (mUiAutomationConnection != null) {
+ if (mUiAutomation == null) {
+ mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
+ mUiAutomationConnection);
+ mUiAutomation.connect();
+ }
+ return mUiAutomation;
+ }
+ return null;
+ }
+
private final class InstrumentationThread extends Thread {
public InstrumentationThread(String name) {
super(name);
}
public void run() {
- IActivityManager am = ActivityManagerNative.getDefault();
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
} catch (RuntimeException e) {
@@ -1661,7 +1701,7 @@ public class Instrumentation {
onStart();
}
}
-
+
private static final class EmptyRunnable implements Runnable {
public void run() {
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 0a9ed58..2224490 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -362,7 +362,12 @@ public final class LoadedApk {
try {
pi = pm.getPackageInfo(mPackageName, 0, UserHandle.myUserId());
} catch (RemoteException e) {
- throw new AssertionError(e);
+ throw new IllegalStateException("Unable to get package info for "
+ + mPackageName + "; is system dying?", e);
+ }
+ if (pi == null) {
+ throw new IllegalStateException("Unable to get package info for "
+ + mPackageName + "; is package not installed?");
}
/*
* Two possible indications that this package could be
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index a1a147a..7e0a27a 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -123,13 +123,13 @@ public class MediaRouteButton extends View {
if (mToggleMode) {
if (mRemoteActive) {
- mRouter.selectRouteInt(mRouteTypes, mRouter.getSystemAudioRoute());
+ mRouter.selectRouteInt(mRouteTypes, mRouter.getDefaultRoute());
} else {
final int N = mRouter.getRouteCount();
for (int i = 0; i < N; i++) {
final RouteInfo route = mRouter.getRouteAt(i);
if ((route.getSupportedTypes() & mRouteTypes) != 0 &&
- route != mRouter.getSystemAudioRoute()) {
+ route != mRouter.getDefaultRoute()) {
mRouter.selectRouteInt(mRouteTypes, route);
}
}
@@ -216,7 +216,7 @@ public class MediaRouteButton extends View {
void updateRemoteIndicator() {
final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes);
- final boolean isRemote = selected != mRouter.getSystemAudioRoute();
+ final boolean isRemote = selected != mRouter.getDefaultRoute();
final boolean isConnecting = selected != null &&
selected.getStatusCode() == RouteInfo.STATUS_CONNECTING;
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index 396f910..63c6acd 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -1,7 +1,20 @@
+/*
+ * 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.app;
-import com.android.internal.view.IInputMethodSession;
-
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
@@ -14,7 +27,6 @@ import android.os.Environment;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.AttributeSet;
-import android.view.InputChannel;
import android.view.InputQueue;
import android.view.KeyEvent;
import android.view.Surface;
@@ -25,7 +37,6 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.inputmethod.InputMethodManager;
import java.io.File;
-import java.lang.ref.WeakReference;
/**
* Convenience for implementing an activity that will be implemented
@@ -65,7 +76,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
private NativeContentView mNativeContentView;
private InputMethodManager mIMM;
- private InputMethodCallback mInputMethodCallback;
private int mNativeHandle;
@@ -100,11 +110,9 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
int format, int width, int height);
private native void onSurfaceRedrawNeededNative(int handle, Surface surface);
private native void onSurfaceDestroyedNative(int handle);
- private native void onInputChannelCreatedNative(int handle, InputChannel channel);
- private native void onInputChannelDestroyedNative(int handle, InputChannel channel);
+ private native void onInputQueueCreatedNative(int handle, int queuePtr);
+ private native void onInputQueueDestroyedNative(int handle, int queuePtr);
private native void onContentRectChangedNative(int handle, int x, int y, int w, int h);
- private native void dispatchKeyEventNative(int handle, KeyEvent event);
- private native void finishPreDispatchKeyEventNative(int handle, int seq, boolean handled);
static class NativeContentView extends View {
NativeActivity mActivity;
@@ -117,22 +125,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
super(context, attrs);
}
}
-
- static final class InputMethodCallback implements InputMethodManager.FinishedEventCallback {
- WeakReference<NativeActivity> mNa;
-
- InputMethodCallback(NativeActivity na) {
- mNa = new WeakReference<NativeActivity>(na);
- }
-
- @Override
- public void finishedEvent(int seq, boolean handled) {
- NativeActivity na = mNa.get();
- if (na != null) {
- na.finishPreDispatchKeyEventNative(na.mNativeHandle, seq, handled);
- }
- }
- }
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -141,7 +133,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
ActivityInfo ai;
mIMM = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
- mInputMethodCallback = new InputMethodCallback(this);
getWindow().takeSurface(this);
getWindow().takeInputQueue(this);
@@ -203,7 +194,7 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
mCurSurfaceHolder = null;
}
if (mCurInputQueue != null) {
- onInputChannelDestroyedNative(mNativeHandle, mCurInputQueue.getInputChannel());
+ onInputQueueDestroyedNative(mNativeHandle, mCurInputQueue.getNativePtr());
mCurInputQueue = null;
}
unloadNativeCode(mNativeHandle);
@@ -267,18 +258,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
}
}
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (mDispatchingUnhandledKey) {
- return super.dispatchKeyEvent(event);
- } else {
- // Key events from the IME do not go through the input channel;
- // we need to intercept them here to hand to the application.
- dispatchKeyEventNative(mNativeHandle, event);
- return true;
- }
- }
-
public void surfaceCreated(SurfaceHolder holder) {
if (!mDestroyed) {
mCurSurfaceHolder = holder;
@@ -310,14 +289,14 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
public void onInputQueueCreated(InputQueue queue) {
if (!mDestroyed) {
mCurInputQueue = queue;
- onInputChannelCreatedNative(mNativeHandle, queue.getInputChannel());
+ onInputQueueCreatedNative(mNativeHandle, queue.getNativePtr());
}
}
public void onInputQueueDestroyed(InputQueue queue) {
- mCurInputQueue = null;
if (!mDestroyed) {
- onInputChannelDestroyedNative(mNativeHandle, queue.getInputChannel());
+ onInputQueueDestroyedNative(mNativeHandle, queue.getNativePtr());
+ mCurInputQueue = null;
}
}
@@ -338,25 +317,6 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
}
}
- boolean dispatchUnhandledKeyEvent(KeyEvent event) {
- try {
- mDispatchingUnhandledKey = true;
- View decor = getWindow().getDecorView();
- if (decor != null) {
- return decor.dispatchKeyEvent(event);
- } else {
- return false;
- }
- } finally {
- mDispatchingUnhandledKey = false;
- }
- }
-
- void preDispatchKeyEvent(KeyEvent event, int seq) {
- mIMM.dispatchKeyEvent(this, seq, event,
- mInputMethodCallback);
- }
-
void setWindowFlags(int flags, int mask) {
getWindow().setFlags(flags, mask);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 3f8e16c..b66e95b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -24,16 +24,14 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.net.Uri;
+import android.os.BadParcelableException;
import android.os.Bundle;
-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;
-import android.util.Slog;
import android.util.TypedValue;
import android.view.View;
import android.widget.ProgressBar;
@@ -58,6 +56,8 @@ import java.util.ArrayList;
*/
public class Notification implements Parcelable
{
+ private static final String TAG = "Notification";
+
/**
* Use all default values (where applicable).
*/
@@ -431,19 +431,54 @@ public class Notification implements Parcelable
public String[] kind;
/**
- * Extra key for people values (type TBD).
- *
+ * Additional semantic data to be carried around with this Notification.
* @hide
*/
+ public Bundle extras = new Bundle();
+
+ // extras keys for Builder inputs
+ /** @hide */
+ public static final String EXTRA_TITLE = "android.title";
+ /** @hide */
+ public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
+ /** @hide */
+ public static final String EXTRA_TEXT = "android.text";
+ /** @hide */
+ public static final String EXTRA_SUB_TEXT = "android.subText";
+ /** @hide */
+ public static final String EXTRA_INFO_TEXT = "android.infoText";
+ /** @hide */
+ public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
+ /** @hide */
+ public static final String EXTRA_SMALL_ICON = "android.icon";
+ /** @hide */
+ public static final String EXTRA_LARGE_ICON = "android.largeIcon";
+ /** @hide */
+ public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
+ /** @hide */
+ public static final String EXTRA_PROGRESS = "android.progress";
+ /** @hide */
+ public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
+ /** @hide */
+ public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
+ /** @hide */
+ public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
+ /** @hide */
+ public static final String EXTRA_SHOW_WHEN = "android.showWhen";
+ /** @hide from BigPictureStyle */
+ public static final String EXTRA_PICTURE = "android.picture";
+ /** @hide from InboxStyle */
+ public static final String EXTRA_TEXT_LINES = "android.textLines";
+
+ // extras keys for other interesting pieces of information
+ /** @hide */
public static final String EXTRA_PEOPLE = "android.people";
- private Bundle extras;
-
/**
* Structure to encapsulate an "action", including title and icon, that can be attached to a Notification.
* @hide
*/
- private static class Action implements Parcelable {
+ public static class Action implements Parcelable {
public int icon;
public CharSequence title;
public PendingIntent actionIntent;
@@ -495,7 +530,10 @@ public class Notification implements Parcelable
};
}
- private Action[] actions;
+ /**
+ * @hide
+ */
+ public Action[] actions;
/**
* Constructs a Notification object with default values.
@@ -589,11 +627,10 @@ public class Notification implements Parcelable
kind = parcel.createStringArray(); // may set kind to null
- if (parcel.readInt() != 0) {
- extras = parcel.readBundle();
- }
+ extras = parcel.readBundle(); // may be null
+
+ actions = parcel.createTypedArray(Action.CREATOR); // may be null
- actions = parcel.createTypedArray(Action.CREATOR);
if (parcel.readInt() != 0) {
bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
}
@@ -602,7 +639,16 @@ public class Notification implements Parcelable
@Override
public Notification clone() {
Notification that = new Notification();
+ cloneInto(that, true);
+ return that;
+ }
+ /**
+ * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members
+ * of this into that.
+ * @hide
+ */
+ public void cloneInto(Notification that, boolean heavy) {
that.when = this.when;
that.icon = this.icon;
that.number = this.number;
@@ -615,13 +661,13 @@ public class Notification implements Parcelable
if (this.tickerText != null) {
that.tickerText = this.tickerText.toString();
}
- if (this.tickerView != null) {
+ if (heavy && this.tickerView != null) {
that.tickerView = this.tickerView.clone();
}
- if (this.contentView != null) {
+ if (heavy && this.contentView != null) {
that.contentView = this.contentView.clone();
}
- if (this.largeIcon != null) {
+ if (heavy && this.largeIcon != null) {
that.largeIcon = Bitmap.createBitmap(this.largeIcon);
}
that.iconLevel = this.iconLevel;
@@ -652,19 +698,62 @@ public class Notification implements Parcelable
}
if (this.extras != null) {
- that.extras = new Bundle(this.extras);
-
+ try {
+ that.extras = new Bundle(this.extras);
+ // will unparcel
+ that.extras.size();
+ } catch (BadParcelableException e) {
+ Log.e(TAG, "could not unparcel extras from notification: " + this, e);
+ that.extras = null;
+ }
}
- that.actions = new Action[this.actions.length];
- for(int i=0; i<this.actions.length; i++) {
- that.actions[i] = this.actions[i].clone();
+ if (this.actions != null) {
+ that.actions = new Action[this.actions.length];
+ for(int i=0; i<this.actions.length; i++) {
+ that.actions[i] = this.actions[i].clone();
+ }
}
- if (this.bigContentView != null) {
+
+ if (heavy && this.bigContentView != null) {
that.bigContentView = this.bigContentView.clone();
}
- return that;
+ if (!heavy) {
+ that.lightenPayload(); // will clean out extras
+ }
+ }
+
+ /**
+ * Removes heavyweight parts of the Notification object for archival or for sending to
+ * listeners when the full contents are not necessary.
+ * @hide
+ */
+ public final void lightenPayload() {
+ tickerView = null;
+ contentView = null;
+ bigContentView = null;
+ largeIcon = null;
+ if (extras != null) {
+ extras.remove(Notification.EXTRA_LARGE_ICON);
+ extras.remove(Notification.EXTRA_LARGE_ICON_BIG);
+ extras.remove(Notification.EXTRA_PICTURE);
+ }
+ }
+
+ /**
+ * Make sure this CharSequence is safe to put into a bundle, which basically
+ * means it had better not be some custom Parcelable implementation.
+ * @hide
+ */
+ public static CharSequence safeCharSequence(CharSequence cs) {
+ if (cs instanceof Parcelable) {
+ Log.e(TAG, "warning: " + cs.getClass().getCanonicalName()
+ + " instance is a custom Parcelable and not allowed in Notification");
+ return cs.toString();
+ }
+
+ return cs;
}
public int describeContents() {
@@ -745,14 +834,9 @@ public class Notification implements Parcelable
parcel.writeStringArray(kind); // ok for null
- if (extras != null) {
- parcel.writeInt(1);
- extras.writeToParcel(parcel, 0);
- } else {
- parcel.writeInt(0);
- }
+ parcel.writeBundle(extras); // null ok
- parcel.writeTypedArray(actions, 0);
+ parcel.writeTypedArray(actions, 0); // null ok
if (bigContentView != null) {
parcel.writeInt(1);
@@ -800,35 +884,29 @@ public class Notification implements Parcelable
@Deprecated
public void setLatestEventInfo(Context context,
CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
- // TODO: rewrite this to use Builder
- RemoteViews contentView = new RemoteViews(context.getPackageName(),
- R.layout.notification_template_base);
- if (this.icon != 0) {
- contentView.setImageViewResource(R.id.icon, this.icon);
- }
- if (priority < PRIORITY_LOW) {
- contentView.setInt(R.id.icon,
- "setBackgroundResource", R.drawable.notification_template_icon_low_bg);
- contentView.setInt(R.id.status_bar_latest_event_content,
- "setBackgroundResource", R.drawable.notification_bg_low);
- }
+ Notification.Builder builder = new Notification.Builder(context);
+
+ // First, ensure that key pieces of information that may have been set directly
+ // are preserved
+ builder.setWhen(this.when);
+ builder.setSmallIcon(this.icon);
+ builder.setPriority(this.priority);
+ builder.setTicker(this.tickerText);
+ builder.setNumber(this.number);
+ builder.mFlags = this.flags;
+ builder.setSound(this.sound, this.audioStreamType);
+ builder.setDefaults(this.defaults);
+ builder.setVibrate(this.vibrate);
+
+ // now apply the latestEventInfo fields
if (contentTitle != null) {
- contentView.setTextViewText(R.id.title, contentTitle);
+ builder.setContentTitle(contentTitle);
}
if (contentText != null) {
- contentView.setTextViewText(R.id.text, contentText);
- }
- if (this.when != 0) {
- contentView.setViewVisibility(R.id.time, View.VISIBLE);
- contentView.setLong(R.id.time, "setTime", when);
- }
- if (this.number != 0) {
- NumberFormat f = NumberFormat.getIntegerInstance();
- contentView.setTextViewText(R.id.info, f.format(this.number));
+ builder.setContentText(contentText);
}
-
- this.contentView = contentView;
- this.contentIntent = contentIntent;
+ builder.setContentIntent(contentIntent);
+ builder.buildInto(this);
}
@Override
@@ -1075,7 +1153,7 @@ public class Notification implements Parcelable
* Set the first line of text in the platform notification template.
*/
public Builder setContentTitle(CharSequence title) {
- mContentTitle = title;
+ mContentTitle = safeCharSequence(title);
return this;
}
@@ -1083,16 +1161,17 @@ public class Notification implements Parcelable
* Set the second line of text in the platform notification template.
*/
public Builder setContentText(CharSequence text) {
- mContentText = text;
+ mContentText = safeCharSequence(text);
return this;
}
/**
* Set the third line of text in the platform notification template.
- * Don't use if you're also using {@link #setProgress(int, int, boolean)}; they occupy the same location in the standard template.
+ * Don't use if you're also using {@link #setProgress(int, int, boolean)}; they occupy the
+ * same location in the standard template.
*/
public Builder setSubText(CharSequence text) {
- mSubText = text;
+ mSubText = safeCharSequence(text);
return this;
}
@@ -1113,7 +1192,7 @@ public class Notification implements Parcelable
* right (to the right of a smallIcon if it has been placed there).
*/
public Builder setContentInfo(CharSequence info) {
- mContentInfo = info;
+ mContentInfo = safeCharSequence(info);
return this;
}
@@ -1193,7 +1272,7 @@ public class Notification implements Parcelable
* @see Notification#tickerText
*/
public Builder setTicker(CharSequence tickerText) {
- mTickerText = tickerText;
+ mTickerText = safeCharSequence(tickerText);
return this;
}
@@ -1206,7 +1285,7 @@ public class Notification implements Parcelable
* @see Notification#tickerView
*/
public Builder setTicker(CharSequence tickerText, RemoteViews views) {
- mTickerText = tickerText;
+ mTickerText = safeCharSequence(tickerText);
mTickerView = views;
return this;
}
@@ -1389,7 +1468,7 @@ public class Notification implements Parcelable
* @param intent PendingIntent to be fired when the action is invoked.
*/
public Builder addAction(int icon, CharSequence title, PendingIntent intent) {
- mActions.add(new Action(icon, title, intent));
+ mActions.add(new Action(icon, safeCharSequence(title), intent));
return this;
}
@@ -1602,7 +1681,7 @@ public class Notification implements Parcelable
n.defaults = mDefaults;
n.flags = mFlags;
n.bigContentView = makeBigContentView();
- if (mLedOnMs != 0 && mLedOffMs != 0) {
+ if (mLedOnMs != 0 || mLedOffMs != 0) {
n.flags |= FLAG_SHOW_LIGHTS;
}
if ((mDefaults & DEFAULT_LIGHTS) != 0) {
@@ -1615,15 +1694,37 @@ public class Notification implements Parcelable
n.kind = null;
}
n.priority = mPriority;
- n.extras = mExtras != null ? new Bundle(mExtras) : null;
if (mActions.size() > 0) {
n.actions = new Action[mActions.size()];
mActions.toArray(n.actions);
}
+
return n;
}
/**
+ * Capture, in the provided bundle, semantic information used in the construction of
+ * this Notification object.
+ * @hide
+ */
+ public void addExtras(Bundle extras) {
+ // Store original information used in the construction of this object
+ extras.putCharSequence(EXTRA_TITLE, mContentTitle);
+ extras.putCharSequence(EXTRA_TEXT, mContentText);
+ extras.putCharSequence(EXTRA_SUB_TEXT, mSubText);
+ extras.putCharSequence(EXTRA_INFO_TEXT, mContentInfo);
+ extras.putInt(EXTRA_SMALL_ICON, mSmallIcon);
+ extras.putInt(EXTRA_PROGRESS, mProgress);
+ extras.putInt(EXTRA_PROGRESS_MAX, mProgressMax);
+ extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate);
+ extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer);
+ extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen);
+ if (mLargeIcon != null) {
+ extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
+ }
+ }
+
+ /**
* @deprecated Use {@link #build()} instead.
*/
@Deprecated
@@ -1636,14 +1737,34 @@ public class Notification implements Parcelable
* object.
*/
public Notification build() {
+ final Notification n;
+
if (mStyle != null) {
- return mStyle.build();
+ n = mStyle.build();
} else {
- return buildUnstyled();
+ n = buildUnstyled();
+ }
+
+ n.extras = mExtras != null ? new Bundle(mExtras) : new Bundle();
+
+ addExtras(n.extras);
+ if (mStyle != null) {
+ mStyle.addExtras(n.extras);
}
+
+ return n;
}
- }
+ /**
+ * Apply this Builder to an existing {@link Notification} object.
+ *
+ * @hide
+ */
+ public Notification buildInto(Notification n) {
+ build().cloneInto(n, true);
+ return n;
+ }
+ }
/**
* An object that can apply a rich notification style to a {@link Notification.Builder}
@@ -1719,6 +1840,18 @@ public class Notification implements Parcelable
return contentView;
}
+ /**
+ * @hide
+ */
+ public void addExtras(Bundle extras) {
+ if (mSummaryTextSet) {
+ extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText);
+ }
+ if (mBigContentTitle != null) {
+ extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle);
+ }
+ }
+
public abstract Notification build();
}
@@ -1756,7 +1889,7 @@ public class Notification implements Parcelable
* This defaults to the value passed to setContentTitle().
*/
public BigPictureStyle setBigContentTitle(CharSequence title) {
- internalSetBigContentTitle(title);
+ internalSetBigContentTitle(safeCharSequence(title));
return this;
}
@@ -1764,7 +1897,7 @@ public class Notification implements Parcelable
* Set the first line of text after the detail section in the big form of the template.
*/
public BigPictureStyle setSummaryText(CharSequence cs) {
- internalSetSummaryText(cs);
+ internalSetSummaryText(safeCharSequence(cs));
return this;
}
@@ -1793,6 +1926,18 @@ public class Notification implements Parcelable
return contentView;
}
+ /**
+ * @hide
+ */
+ public void addExtras(Bundle extras) {
+ super.addExtras(extras);
+
+ if (mBigLargeIconSet) {
+ extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon);
+ }
+ extras.putParcelable(EXTRA_PICTURE, mPicture);
+ }
+
@Override
public Notification build() {
checkBuilder();
@@ -1837,7 +1982,7 @@ public class Notification implements Parcelable
* This defaults to the value passed to setContentTitle().
*/
public BigTextStyle setBigContentTitle(CharSequence title) {
- internalSetBigContentTitle(title);
+ internalSetBigContentTitle(safeCharSequence(title));
return this;
}
@@ -1845,7 +1990,7 @@ public class Notification implements Parcelable
* Set the first line of text after the detail section in the big form of the template.
*/
public BigTextStyle setSummaryText(CharSequence cs) {
- internalSetSummaryText(cs);
+ internalSetSummaryText(safeCharSequence(cs));
return this;
}
@@ -1854,10 +1999,19 @@ public class Notification implements Parcelable
* template in place of the content text.
*/
public BigTextStyle bigText(CharSequence cs) {
- mBigText = cs;
+ mBigText = safeCharSequence(cs);
return this;
}
+ /**
+ * @hide
+ */
+ public void addExtras(Bundle extras) {
+ super.addExtras(extras);
+
+ extras.putCharSequence(EXTRA_TEXT, mBigText);
+ }
+
private RemoteViews makeBigContentView() {
// Remove the content text so line3 only shows if you have a summary
final boolean hadThreeLines = (mBuilder.mContentText != null && mBuilder.mSubText != null);
@@ -1882,6 +2036,9 @@ public class Notification implements Parcelable
checkBuilder();
Notification wip = mBuilder.buildUnstyled();
wip.bigContentView = makeBigContentView();
+
+ wip.extras.putCharSequence(EXTRA_TEXT, mBigText);
+
return wip;
}
}
@@ -1921,7 +2078,7 @@ public class Notification implements Parcelable
* This defaults to the value passed to setContentTitle().
*/
public InboxStyle setBigContentTitle(CharSequence title) {
- internalSetBigContentTitle(title);
+ internalSetBigContentTitle(safeCharSequence(title));
return this;
}
@@ -1929,7 +2086,7 @@ public class Notification implements Parcelable
* Set the first line of text after the detail section in the big form of the template.
*/
public InboxStyle setSummaryText(CharSequence cs) {
- internalSetSummaryText(cs);
+ internalSetSummaryText(safeCharSequence(cs));
return this;
}
@@ -1937,10 +2094,19 @@ public class Notification implements Parcelable
* Append a line to the digest section of the Inbox notification.
*/
public InboxStyle addLine(CharSequence cs) {
- mTexts.add(cs);
+ mTexts.add(safeCharSequence(cs));
return this;
}
+ /**
+ * @hide
+ */
+ public void addExtras(Bundle extras) {
+ super.addExtras(extras);
+ CharSequence[] a = new CharSequence[mTexts.size()];
+ extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a));
+ }
+
private RemoteViews makeBigContentView() {
// Remove the content text so line3 disappears unless you have a summary
mBuilder.mContentText = null;
@@ -1981,6 +2147,7 @@ public class Notification implements Parcelable
checkBuilder();
Notification wip = mBuilder.buildUnstyled();
wip.bigContentView = makeBigContentView();
+
return wip;
}
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 0acad75..dbafc78 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -21,6 +21,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.StrictMode;
import android.os.UserHandle;
import android.util.Log;
@@ -126,11 +127,14 @@ public class NotificationManager
String pkg = mContext.getPackageName();
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
+ if (StrictMode.vmFileUriExposureEnabled()) {
+ notification.sound.checkFileUriExposed("Notification.sound");
+ }
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
try {
- service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
- UserHandle.myUserId());
+ service.enqueueNotificationWithTag(pkg, mContext.getBasePackageName(), tag, id,
+ notification, idOut, UserHandle.myUserId());
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
@@ -148,11 +152,14 @@ public class NotificationManager
String pkg = mContext.getPackageName();
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
+ if (StrictMode.vmFileUriExposureEnabled()) {
+ notification.sound.checkFileUriExposed("Notification.sound");
+ }
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
try {
- service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
- user.getIdentifier());
+ service.enqueueNotificationWithTag(pkg, mContext.getBasePackageName(), tag, id,
+ notification, idOut, user.getIdentifier());
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 2897ee0..25c790f 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -41,7 +41,7 @@ import android.util.AndroidException;
* you are granting it the right to perform the operation you have specified
* as if the other application was yourself (with the same permissions and
* identity). As such, you should be careful about how you build the PendingIntent:
- * often, for example, the base Intent you supply will have the component
+ * almost always, for example, the base Intent you supply should have the component
* name explicitly set to one of your own components, to ensure it is ultimately
* sent there and nowhere else.
*
@@ -200,6 +200,11 @@ public final class PendingIntent implements Parcelable {
* existing activity, so you must use the {@link Intent#FLAG_ACTIVITY_NEW_TASK
* Intent.FLAG_ACTIVITY_NEW_TASK} launch flag in the Intent.
*
+ * <p class="note">For security reasons, the {@link android.content.Intent}
+ * you supply here should almost always be an <em>explicit intent</em>,
+ * that is specify an explicit component to be delivered to through
+ * {@link Intent#setClass(android.content.Context, Class)} Intent.setClass</p>
+ *
* @param context The Context in which this PendingIntent should start
* the activity.
* @param requestCode Private request code for the sender (currently
@@ -227,6 +232,11 @@ public final class PendingIntent implements Parcelable {
* existing activity, so you must use the {@link Intent#FLAG_ACTIVITY_NEW_TASK
* Intent.FLAG_ACTIVITY_NEW_TASK} launch flag in the Intent.
*
+ * <p class="note">For security reasons, the {@link android.content.Intent}
+ * you supply here should almost always be an <em>explicit intent</em>,
+ * that is specify an explicit component to be delivered to through
+ * {@link Intent#setClass(android.content.Context, Class)} Intent.setClass</p>
+ *
* @param context The Context in which this PendingIntent should start
* the activity.
* @param requestCode Private request code for the sender (currently
@@ -250,7 +260,8 @@ public final class PendingIntent implements Parcelable {
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
try {
- intent.setAllowFds(false);
+ intent.migrateExtraStreamToClipData();
+ intent.prepareToLeaveProcess();
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
@@ -274,7 +285,8 @@ public final class PendingIntent implements Parcelable {
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
try {
- intent.setAllowFds(false);
+ intent.migrateExtraStreamToClipData();
+ intent.prepareToLeaveProcess();
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
@@ -313,6 +325,11 @@ public final class PendingIntent implements Parcelable {
* UI the user actually sees when the intents are started.
* </p>
*
+ * <p class="note">For security reasons, the {@link android.content.Intent} objects
+ * you supply here should almost always be <em>explicit intents</em>,
+ * that is specify an explicit component to be delivered to through
+ * {@link Intent#setClass(android.content.Context, Class)} Intent.setClass</p>
+ *
* @param context The Context in which this PendingIntent should start
* the activity.
* @param requestCode Private request code for the sender (currently
@@ -359,6 +376,11 @@ public final class PendingIntent implements Parcelable {
* UI the user actually sees when the intents are started.
* </p>
*
+ * <p class="note">For security reasons, the {@link android.content.Intent} objects
+ * you supply here should almost always be <em>explicit intents</em>,
+ * that is specify an explicit component to be delivered to through
+ * {@link Intent#setClass(android.content.Context, Class)} Intent.setClass</p>
+ *
* @param context The Context in which this PendingIntent should start
* the activity.
* @param requestCode Private request code for the sender (currently
@@ -379,7 +401,8 @@ public final class PendingIntent implements Parcelable {
String packageName = context.getPackageName();
String[] resolvedTypes = new String[intents.length];
for (int i=0; i<intents.length; i++) {
- intents[i].setAllowFds(false);
+ intents[i].migrateExtraStreamToClipData();
+ intents[i].prepareToLeaveProcess();
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver());
}
try {
@@ -404,7 +427,8 @@ public final class PendingIntent implements Parcelable {
String packageName = context.getPackageName();
String[] resolvedTypes = new String[intents.length];
for (int i=0; i<intents.length; i++) {
- intents[i].setAllowFds(false);
+ intents[i].migrateExtraStreamToClipData();
+ intents[i].prepareToLeaveProcess();
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver());
}
try {
@@ -423,6 +447,11 @@ public final class PendingIntent implements Parcelable {
* Retrieve a PendingIntent that will perform a broadcast, like calling
* {@link Context#sendBroadcast(Intent) Context.sendBroadcast()}.
*
+ * <p class="note">For security reasons, the {@link android.content.Intent}
+ * you supply here should almost always be an <em>explicit intent</em>,
+ * that is specify an explicit component to be delivered to through
+ * {@link Intent#setClass(android.content.Context, Class)} Intent.setClass</p>
+ *
* @param context The Context in which this PendingIntent should perform
* the broadcast.
* @param requestCode Private request code for the sender (currently
@@ -455,7 +484,7 @@ public final class PendingIntent implements Parcelable {
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
ActivityManager.INTENT_SENDER_BROADCAST, packageName,
@@ -473,6 +502,11 @@ public final class PendingIntent implements Parcelable {
* {@link Context#startService Context.startService()}. The start
* arguments given to the service will come from the extras of the Intent.
*
+ * <p class="note">For security reasons, the {@link android.content.Intent}
+ * you supply here should almost always be an <em>explicit intent</em>,
+ * that is specify an explicit component to be delivered to through
+ * {@link Intent#setClass(android.content.Context, Class)} Intent.setClass</p>
+ *
* @param context The Context in which this PendingIntent should start
* the service.
* @param requestCode Private request code for the sender (currently
@@ -494,7 +528,7 @@ public final class PendingIntent implements Parcelable {
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
try {
- intent.setAllowFds(false);
+ intent.prepareToLeaveProcess();
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
ActivityManager.INTENT_SENDER_SERVICE, packageName,
@@ -707,6 +741,15 @@ public final class PendingIntent implements Parcelable {
* sending the Intent. The returned string is supplied by the system, so
* that an application can not spoof its package.
*
+ * <p class="note">Be careful about how you use this. All this tells you is
+ * who created the PendingIntent. It does <strong>not</strong> tell you who
+ * handed the PendingIntent to you: that is, PendingIntent objects are intended to be
+ * passed between applications, so the PendingIntent you receive from an application
+ * could actually be one it received from another application, meaning the result
+ * you get here will identify the original application. Because of this, you should
+ * only use this information to identify who you expect to be interacting with
+ * through a {@link #send} call, not who gave you the PendingIntent.</p>
+ *
* @return The package name of the PendingIntent, or null if there is
* none associated with it.
*/
@@ -726,6 +769,15 @@ public final class PendingIntent implements Parcelable {
* sending the Intent. The returned integer is supplied by the system, so
* that an application can not spoof its uid.
*
+ * <p class="note">Be careful about how you use this. All this tells you is
+ * who created the PendingIntent. It does <strong>not</strong> tell you who
+ * handed the PendingIntent to you: that is, PendingIntent objects are intended to be
+ * passed between applications, so the PendingIntent you receive from an application
+ * could actually be one it received from another application, meaning the result
+ * you get here will identify the original application. Because of this, you should
+ * only use this information to identify who you expect to be interacting with
+ * through a {@link #send} call, not who gave you the PendingIntent.</p>
+ *
* @return The uid of the PendingIntent, or -1 if there is
* none associated with it.
*/
@@ -747,6 +799,15 @@ public final class PendingIntent implements Parcelable {
* {@link android.os.Process#myUserHandle() Process.myUserHandle()} for
* more explanation of user handles.
*
+ * <p class="note">Be careful about how you use this. All this tells you is
+ * who created the PendingIntent. It does <strong>not</strong> tell you who
+ * handed the PendingIntent to you: that is, PendingIntent objects are intended to be
+ * passed between applications, so the PendingIntent you receive from an application
+ * could actually be one it received from another application, meaning the result
+ * you get here will identify the original application. Because of this, you should
+ * only use this information to identify who you expect to be interacting with
+ * through a {@link #send} call, not who gave you the PendingIntent.</p>
+ *
* @return The user handle of the PendingIntent, or null if there is
* none associated with it.
*/
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 6382cee..7dfc589 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -846,8 +846,8 @@ public class SearchManager
*
* @hide
*/
- public Intent getAssistIntent(Context context) {
- return getAssistIntent(context, UserHandle.myUserId());
+ public Intent getAssistIntent(Context context, boolean inclContext) {
+ return getAssistIntent(context, inclContext, UserHandle.myUserId());
}
/**
@@ -856,7 +856,7 @@ public class SearchManager
*
* @hide
*/
- public Intent getAssistIntent(Context context, int userHandle) {
+ public Intent getAssistIntent(Context context, boolean inclContext, int userHandle) {
try {
if (mService == null) {
return null;
@@ -867,6 +867,13 @@ public class SearchManager
}
Intent intent = new Intent(Intent.ACTION_ASSIST);
intent.setComponent(comp);
+ if (inclContext) {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ Bundle extras = am.getTopActivityExtras(0);
+ if (extras != null) {
+ intent.replaceExtras(extras);
+ }
+ }
return intent;
} catch (RemoteException re) {
Log.e(TAG, "getAssistIntent() failed: " + re);
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 4fbca73..3967740 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -633,7 +633,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
*
* @param id The identifier for this notification as per
* {@link NotificationManager#notify(int, Notification)
- * NotificationManager.notify(int, Notification)}.
+ * NotificationManager.notify(int, Notification)}; must not be 0.
* @param notification The Notification to be displayed.
*
* @see #stopForeground(boolean)
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
new file mode 100644
index 0000000..498fa42
--- /dev/null
+++ b/core/java/android/app/UiAutomation.java
@@ -0,0 +1,731 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.accessibilityservice.AccessibilityService.Callbacks;
+import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Display;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.Surface;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Class for interacting with the device's UI by simulation user actions and
+ * introspection of the screen content. It relies on the platform accessibility
+ * APIs to introspect the screen and to perform some actions on the remote view
+ * tree. It also allows injecting of arbitrary raw input events simulating user
+ * interaction with keyboards and touch devices. One can think of a UiAutomation
+ * as a special type of {@link android.accessibilityservice.AccessibilityService}
+ * which does not provide hooks for the service life cycle and exposes other
+ * APIs that are useful for UI test automation.
+ * <p>
+ * The APIs exposed by this class are low-level to maximize flexibility when
+ * developing UI test automation tools and libraries. Generally, a UiAutomation
+ * client should be using a higher-level library or implement high-level functions.
+ * For example, performing a tap on the screen requires construction and injecting
+ * of a touch down and up events which have to be delivered to the system by a
+ * call to {@link #injectInputEvent(InputEvent, boolean)}.
+ * </p>
+ * <p>
+ * The APIs exposed by this class operate across applications enabling a client
+ * to write tests that cover use cases spanning over multiple applications. For
+ * example, going to the settings application to change a setting and then
+ * interacting with another application whose behavior depends on that setting.
+ * </p>
+ */
+public final class UiAutomation {
+
+ private static final String LOG_TAG = UiAutomation.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ private static final int CONNECTION_ID_UNDEFINED = -1;
+
+ private static final long CONNECT_TIMEOUT_MILLIS = 5000;
+
+ /** Rotation constant: Unfreeze rotation (rotating the device changes its rotation state). */
+ public static final int ROTATION_UNFREEZE = -2;
+
+ /** Rotation constant: Freeze rotation to its current state. */
+ public static final int ROTATION_FREEZE_CURRENT = -1;
+
+ /** Rotation constant: Freeze rotation to 0 degrees (natural orientation) */
+ public static final int ROTATION_FREEZE_0 = Surface.ROTATION_0;
+
+ /** Rotation constant: Freeze rotation to 90 degrees . */
+ public static final int ROTATION_FREEZE_90 = Surface.ROTATION_90;
+
+ /** Rotation constant: Freeze rotation to 180 degrees . */
+ public static final int ROTATION_FREEZE_180 = Surface.ROTATION_180;
+
+ /** Rotation constant: Freeze rotation to 270 degrees . */
+ public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270;
+
+ private final Object mLock = new Object();
+
+ private final ArrayList<AccessibilityEvent> mEventQueue = new ArrayList<AccessibilityEvent>();
+
+ private final IAccessibilityServiceClient mClient;
+
+ private final IUiAutomationConnection mUiAutomationConnection;
+
+ private int mConnectionId = CONNECTION_ID_UNDEFINED;
+
+ private OnAccessibilityEventListener mOnAccessibilityEventListener;
+
+ private boolean mWaitingForEventDelivery;
+
+ private long mLastEventTimeMillis;
+
+ private boolean mIsConnecting;
+
+ /**
+ * Listener for observing the {@link AccessibilityEvent} stream.
+ */
+ public static interface OnAccessibilityEventListener {
+
+ /**
+ * Callback for receiving an {@link AccessibilityEvent}.
+ * <p>
+ * <strong>Note:</strong> This method is <strong>NOT</strong> executed
+ * on the main test thread. The client is responsible for proper
+ * synchronization.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> It is responsibility of the client
+ * to recycle the received events to minimize object creation.
+ * </p>
+ *
+ * @param event The received event.
+ */
+ public void onAccessibilityEvent(AccessibilityEvent event);
+ }
+
+ /**
+ * Listener for filtering accessibility events.
+ */
+ public static interface AccessibilityEventFilter {
+
+ /**
+ * Callback for determining whether an event is accepted or
+ * it is filtered out.
+ *
+ * @param event The event to process.
+ * @return True if the event is accepted, false to filter it out.
+ */
+ public boolean accept(AccessibilityEvent event);
+ }
+
+ /**
+ * Creates a new instance that will handle callbacks from the accessibility
+ * layer on the thread of the provided looper and perform requests for privileged
+ * operations on the provided connection.
+ *
+ * @param looper The looper on which to execute accessibility callbacks.
+ * @param connection The connection for performing privileged operations.
+ *
+ * @hide
+ */
+ public UiAutomation(Looper looper, IUiAutomationConnection connection) {
+ if (looper == null) {
+ throw new IllegalArgumentException("Looper cannot be null!");
+ }
+ if (connection == null) {
+ throw new IllegalArgumentException("Connection cannot be null!");
+ }
+ mUiAutomationConnection = connection;
+ mClient = new IAccessibilityServiceClientImpl(looper);
+ }
+
+ /**
+ * Connects this UiAutomation to the accessibility introspection APIs.
+ *
+ * @hide
+ */
+ public void connect() {
+ synchronized (mLock) {
+ throwIfConnectedLocked();
+ if (mIsConnecting) {
+ return;
+ }
+ mIsConnecting = true;
+ }
+
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.connect(mClient);
+ } catch (RemoteException re) {
+ throw new RuntimeException("Error while connecting UiAutomation", re);
+ }
+
+ synchronized (mLock) {
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ try {
+ while (true) {
+ if (isConnectedLocked()) {
+ break;
+ }
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = CONNECT_TIMEOUT_MILLIS - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ throw new RuntimeException("Error while connecting UiAutomation");
+ }
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ } finally {
+ mIsConnecting = false;
+ }
+ }
+ }
+
+ /**
+ * Disconnects this UiAutomation from the accessibility introspection APIs.
+ *
+ * @hide
+ */
+ public void disconnect() {
+ synchronized (mLock) {
+ if (mIsConnecting) {
+ throw new IllegalStateException(
+ "Cannot call disconnect() while connecting!");
+ }
+ throwIfNotConnectedLocked();
+ mConnectionId = CONNECTION_ID_UNDEFINED;
+ }
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.disconnect();
+ } catch (RemoteException re) {
+ throw new RuntimeException("Error while disconnecting UiAutomation", re);
+ }
+ }
+
+ /**
+ * The id of the {@link IAccessibilityInteractionConnection} for querying
+ * the screen content. This is here for legacy purposes since some tools use
+ * hidden APIs to introspect the screen.
+ *
+ * @hide
+ */
+ public int getConnectionId() {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ return mConnectionId;
+ }
+ }
+
+ /**
+ * Sets a callback for observing the stream of {@link AccessibilityEvent}s.
+ *
+ * @param listener The callback.
+ */
+ public void setOnAccessibilityEventListener(OnAccessibilityEventListener listener) {
+ synchronized (mLock) {
+ mOnAccessibilityEventListener = listener;
+ }
+ }
+
+ /**
+ * Performs a global action. Such an action can be performed at any moment
+ * regardless of the current application or user location in that application.
+ * For example going back, going home, opening recents, etc.
+ *
+ * @param action The action to perform.
+ * @return Whether the action was successfully performed.
+ *
+ * @see AccessibilityService#GLOBAL_ACTION_BACK
+ * @see AccessibilityService#GLOBAL_ACTION_HOME
+ * @see AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS
+ * @see AccessibilityService#GLOBAL_ACTION_RECENTS
+ */
+ public final boolean performGlobalAction(int action) {
+ final IAccessibilityServiceConnection connection;
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ connection = AccessibilityInteractionClient.getInstance()
+ .getConnection(mConnectionId);
+ }
+ // Calling out without a lock held.
+ if (connection != null) {
+ try {
+ return connection.performGlobalAction(action);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets the an {@link AccessibilityServiceInfo} describing this UiAutomation.
+ * This method is useful if one wants to change some of the dynamically
+ * configurable properties at runtime.
+ *
+ * @return The accessibility service info.
+ *
+ * @see AccessibilityServiceInfo
+ */
+ public final AccessibilityServiceInfo getServiceInfo() {
+ final IAccessibilityServiceConnection connection;
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ connection = AccessibilityInteractionClient.getInstance()
+ .getConnection(mConnectionId);
+ }
+ // Calling out without a lock held.
+ if (connection != null) {
+ try {
+ return connection.getServiceInfo();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the {@link AccessibilityServiceInfo} that describes how this
+ * UiAutomation will be handled by the platform accessibility layer.
+ *
+ * @param info The info.
+ *
+ * @see AccessibilityServiceInfo
+ */
+ public final void setServiceInfo(AccessibilityServiceInfo info) {
+ final IAccessibilityServiceConnection connection;
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ AccessibilityInteractionClient.getInstance().clearCache();
+ connection = AccessibilityInteractionClient.getInstance()
+ .getConnection(mConnectionId);
+ }
+ // Calling out without a lock held.
+ if (connection != null) {
+ try {
+ connection.setServiceInfo(info);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
+ }
+ }
+ }
+
+ /**
+ * Gets the root {@link AccessibilityNodeInfo} in the active window.
+ *
+ * @return The root info.
+ */
+ public AccessibilityNodeInfo getRootInActiveWindow() {
+ final int connectionId;
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ connectionId = mConnectionId;
+ }
+ // Calling out without a lock held.
+ return AccessibilityInteractionClient.getInstance()
+ .getRootInActiveWindow(connectionId);
+ }
+
+ /**
+ * A method for injecting an arbitrary input event.
+ * <p>
+ * <strong>Note:</strong> It is caller's responsibility to recycle the event.
+ * </p>
+ * @param event The event to inject.
+ * @param sync Whether to inject the event synchronously.
+ * @return Whether event injection succeeded.
+ */
+ public boolean injectInputEvent(InputEvent event, boolean sync) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
+ }
+ // Calling out without a lock held.
+ return mUiAutomationConnection.injectInputEvent(event, sync);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while injecting input event!", re);
+ }
+ return false;
+ }
+
+ /**
+ * Sets the device rotation. A client can freeze the rotation in
+ * desired state or freeze the rotation to its current state or
+ * unfreeze the rotation (rotating the device changes its rotation
+ * state).
+ *
+ * @param rotation The desired rotation.
+ * @return Whether the rotation was set successfully.
+ *
+ * @see #ROTATION_FREEZE_0
+ * @see #ROTATION_FREEZE_90
+ * @see #ROTATION_FREEZE_180
+ * @see #ROTATION_FREEZE_270
+ * @see #ROTATION_FREEZE_CURRENT
+ * @see #ROTATION_UNFREEZE
+ */
+ public boolean setRotation(int rotation) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ switch (rotation) {
+ case ROTATION_FREEZE_0:
+ case ROTATION_FREEZE_90:
+ case ROTATION_FREEZE_180:
+ case ROTATION_FREEZE_270:
+ case ROTATION_UNFREEZE:
+ case ROTATION_FREEZE_CURRENT: {
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.setRotation(rotation);
+ return true;
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while setting rotation!", re);
+ }
+ } return false;
+ default: {
+ throw new IllegalArgumentException("Invalid rotation.");
+ }
+ }
+ }
+
+ /**
+ * Executes a command and waits for a specific accessibility event up to a
+ * given wait timeout. To detect a sequence of events one can implement a
+ * filter that keeps track of seen events of the expected sequence and
+ * returns true after the last event of that sequence is received.
+ * <p>
+ * <strong>Note:</strong> It is caller's responsibility to recycle the returned event.
+ * </p>
+ * @param command The command to execute.
+ * @param filter Filter that recognizes the expected event.
+ * @param timeoutMillis The wait timeout in milliseconds.
+ *
+ * @throws TimeoutException If the expected event is not received within the timeout.
+ */
+ public AccessibilityEvent executeAndWaitForEvent(Runnable command,
+ AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException {
+ // Acquire the lock and prepare for receiving events.
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ mEventQueue.clear();
+ // Prepare to wait for an event.
+ mWaitingForEventDelivery = true;
+ }
+
+ // Note: We have to release the lock since calling out with this lock held
+ // can bite. We will correctly filter out events from other interactions,
+ // so starting to collect events before running the action is just fine.
+
+ // We will ignore events from previous interactions.
+ final long executionStartTimeMillis = SystemClock.uptimeMillis();
+ // Execute the command *without* the lock being held.
+ command.run();
+
+ // Acquire the lock and wait for the event.
+ synchronized (mLock) {
+ try {
+ // Wait for the event.
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ while (true) {
+ // Drain the event queue
+ while (!mEventQueue.isEmpty()) {
+ AccessibilityEvent event = mEventQueue.remove(0);
+ // Ignore events from previous interactions.
+ if (event.getEventTime() < executionStartTimeMillis) {
+ continue;
+ }
+ if (filter.accept(event)) {
+ return event;
+ }
+ event.recycle();
+ }
+ // Check if timed out and if not wait.
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ throw new TimeoutException("Expected event not received within: "
+ + timeoutMillis + " ms.");
+ }
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ } finally {
+ mWaitingForEventDelivery = false;
+ mEventQueue.clear();
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Waits for the accessibility event stream to become idle, which is not to
+ * have received an accessibility event within <code>idleTimeoutMillis</code>.
+ * The total time spent to wait for an idle accessibility event stream is bounded
+ * by the <code>globalTimeoutMillis</code>.
+ *
+ * @param idleTimeoutMillis The timeout in milliseconds between two events
+ * to consider the device idle.
+ * @param globalTimeoutMillis The maximal global timeout in milliseconds in
+ * which to wait for an idle state.
+ *
+ * @throws TimeoutException If no idle state was detected within
+ * <code>globalTimeoutMillis.</code>
+ */
+ public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)
+ throws TimeoutException {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ if (mLastEventTimeMillis <= 0) {
+ mLastEventTimeMillis = startTimeMillis;
+ }
+
+ while (true) {
+ final long currentTimeMillis = SystemClock.uptimeMillis();
+ // Did we get idle state within the global timeout?
+ final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
+ final long remainingGlobalTimeMillis =
+ globalTimeoutMillis - elapsedGlobalTimeMillis;
+ if (remainingGlobalTimeMillis <= 0) {
+ throw new TimeoutException("No idle state with idle timeout: "
+ + idleTimeoutMillis + " within global timeout: "
+ + globalTimeoutMillis);
+ }
+ // Did we get an idle state within the idle timeout?
+ final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
+ final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
+ if (remainingIdleTimeMillis <= 0) {
+ return;
+ }
+ try {
+ mLock.wait(remainingIdleTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ /**
+ * Takes a screenshot.
+ *
+ * @return The screenshot bitmap on success, null otherwise.
+ */
+ public Bitmap takeScreenshot() {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ Display display = DisplayManagerGlobal.getInstance()
+ .getRealDisplay(Display.DEFAULT_DISPLAY);
+ Point displaySize = new Point();
+ display.getRealSize(displaySize);
+ final int displayWidth = displaySize.x;
+ final int displayHeight = displaySize.y;
+
+ final float screenshotWidth;
+ final float screenshotHeight;
+
+ final int rotation = display.getRotation();
+ switch (rotation) {
+ case ROTATION_FREEZE_0: {
+ screenshotWidth = displayWidth;
+ screenshotHeight = displayHeight;
+ } break;
+ case ROTATION_FREEZE_90: {
+ screenshotWidth = displayHeight;
+ screenshotHeight = displayWidth;
+ } break;
+ case ROTATION_FREEZE_180: {
+ screenshotWidth = displayWidth;
+ screenshotHeight = displayHeight;
+ } break;
+ case ROTATION_FREEZE_270: {
+ screenshotWidth = displayHeight;
+ screenshotHeight = displayWidth;
+ } break;
+ default: {
+ throw new IllegalArgumentException("Invalid rotation: "
+ + rotation);
+ }
+ }
+
+ // Take the screenshot
+ Bitmap screenShot = null;
+ try {
+ // Calling out without a lock held.
+ screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
+ (int) screenshotHeight);
+ if (screenShot == null) {
+ return null;
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while taking screnshot!", re);
+ return null;
+ }
+
+ // Rotate the screenshot to the current orientation
+ if (rotation != ROTATION_FREEZE_0) {
+ Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(unrotatedScreenShot);
+ canvas.translate(unrotatedScreenShot.getWidth() / 2,
+ unrotatedScreenShot.getHeight() / 2);
+ canvas.rotate(getDegreesForRotation(rotation));
+ canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
+ canvas.drawBitmap(screenShot, 0, 0, null);
+ canvas.setBitmap(null);
+ screenShot = unrotatedScreenShot;
+ }
+
+ // Optimization
+ screenShot.setHasAlpha(false);
+
+ return screenShot;
+ }
+
+ /**
+ * Sets whether this UiAutomation to run in a "monkey" mode. Applications can query whether
+ * they are executed in a "monkey" mode, i.e. run by a test framework, and avoid doing
+ * potentially undesirable actions such as calling 911 or posting on public forums etc.
+ *
+ * @param enable whether to run in a "monkey" mode or not. Default is not.
+ * @see {@link ActivityManager#isUserAMonkey()}
+ */
+ public void setRunAsMonkey(boolean enable) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ ActivityManagerNative.getDefault().setUserIsMonkey(enable);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while setting run as monkey!", re);
+ }
+ }
+
+ private static float getDegreesForRotation(int value) {
+ switch (value) {
+ case Surface.ROTATION_90: {
+ return 360f - 90f;
+ }
+ case Surface.ROTATION_180: {
+ return 360f - 180f;
+ }
+ case Surface.ROTATION_270: {
+ return 360f - 270f;
+ } default: {
+ return 0;
+ }
+ }
+ }
+
+ private boolean isConnectedLocked() {
+ return mConnectionId != CONNECTION_ID_UNDEFINED;
+ }
+
+ private void throwIfConnectedLocked() {
+ if (mConnectionId != CONNECTION_ID_UNDEFINED) {
+ throw new IllegalStateException("UiAutomation not connected!");
+ }
+ }
+
+ private void throwIfNotConnectedLocked() {
+ if (!isConnectedLocked()) {
+ throw new IllegalStateException("UiAutomation not connected!");
+ }
+ }
+
+ private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
+
+ public IAccessibilityServiceClientImpl(Looper looper) {
+ super(null, looper, new Callbacks() {
+ @Override
+ public void onSetConnectionId(int connectionId) {
+ synchronized (mLock) {
+ mConnectionId = connectionId;
+ mLock.notifyAll();
+ }
+ }
+
+ @Override
+ public void onServiceConnected() {
+ /* do nothing */
+ }
+
+ @Override
+ public void onInterrupt() {
+ /* do nothing */
+ }
+
+ @Override
+ public boolean onGesture(int gestureId) {
+ /* do nothing */
+ return false;
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ synchronized (mLock) {
+ mLastEventTimeMillis = event.getEventTime();
+ if (mWaitingForEventDelivery) {
+ mEventQueue.add(AccessibilityEvent.obtain(event));
+ }
+ mLock.notifyAll();
+ }
+ // Calling out only without a lock held.
+ final OnAccessibilityEventListener listener = mOnAccessibilityEventListener;
+ if (listener != null) {
+ listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
+ }
+ }
+
+ @Override
+ public boolean onKeyEvent(KeyEvent event) {
+ return false;
+ }
+ });
+ }
+ }
+}
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
new file mode 100644
index 0000000..607930c
--- /dev/null
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.hardware.input.InputManager;
+import android.os.Binder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.view.IWindowManager;
+import android.view.InputEvent;
+import android.view.SurfaceControl;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.IAccessibilityManager;
+
+/**
+ * This is a remote object that is passed from the shell to an instrumentation
+ * for enabling access to privileged operations which the shell can do and the
+ * instrumentation cannot. These privileged operations are needed for implementing
+ * a {@link UiAutomation} that enables across application testing by simulating
+ * user actions and performing screen introspection.
+ *
+ * @hide
+ */
+public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
+
+ private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;
+
+ private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService(Service.WINDOW_SERVICE));
+
+ private final Object mLock = new Object();
+
+ private final Binder mToken = new Binder();
+
+ private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
+
+ private IAccessibilityServiceClient mClient;
+
+ private boolean mIsShutdown;
+
+ private int mOwningUid;
+
+ public void connect(IAccessibilityServiceClient client) {
+ if (client == null) {
+ throw new IllegalArgumentException("Client cannot be null!");
+ }
+ synchronized (mLock) {
+ throwIfShutdownLocked();
+ if (isConnectedLocked()) {
+ throw new IllegalStateException("Already connected.");
+ }
+ mOwningUid = Binder.getCallingUid();
+ registerUiTestAutomationServiceLocked(client);
+ storeRotationStateLocked();
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ if (!isConnectedLocked()) {
+ throw new IllegalStateException("Already disconnected.");
+ }
+ mOwningUid = -1;
+ unregisterUiTestAutomationServiceLocked();
+ restoreRotationStateLocked();
+ }
+ }
+
+ @Override
+ public boolean injectInputEvent(InputEvent event, boolean sync) {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
+ : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return InputManager.getInstance().injectInputEvent(event, mode);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean setRotation(int rotation) {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (rotation == UiAutomation.ROTATION_UNFREEZE) {
+ mWindowManager.thawRotation();
+ } else {
+ mWindowManager.freezeRotation(rotation);
+ }
+ return true;
+ } catch (RemoteException re) {
+ /* ignore */
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return false;
+ }
+
+ @Override
+ public Bitmap takeScreenshot(int width, int height) {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return SurfaceControl.screenshot(width, height);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ mIsShutdown = true;
+ if (isConnectedLocked()) {
+ disconnect();
+ }
+ }
+ }
+
+ private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) {
+ IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+ info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
+ info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
+ info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
+ | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+ | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
+ | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
+ try {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ manager.registerUiTestAutomationService(mToken, client, info);
+ mClient = client;
+ } catch (RemoteException re) {
+ throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
+ }
+ }
+
+ private void unregisterUiTestAutomationServiceLocked() {
+ IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
+ try {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ manager.unregisterUiTestAutomationService(mClient);
+ mClient = null;
+ } catch (RemoteException re) {
+ throw new IllegalStateException("Error while unregistering UiTestAutomationService",
+ re);
+ }
+ }
+
+ private void storeRotationStateLocked() {
+ try {
+ if (mWindowManager.isRotationFrozen()) {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ mInitialFrozenRotation = mWindowManager.getRotation();
+ }
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+
+ private void restoreRotationStateLocked() {
+ try {
+ if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ mWindowManager.freezeRotation(mInitialFrozenRotation);
+ } else {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ mWindowManager.thawRotation();
+ }
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+
+ private boolean isConnectedLocked() {
+ return mClient != null;
+ }
+
+ private void throwIfShutdownLocked() {
+ if (mIsShutdown) {
+ throw new IllegalStateException("Connection shutdown!");
+ }
+ }
+
+ private void throwIfNotConnectedLocked() {
+ if (!isConnectedLocked()) {
+ throw new IllegalStateException("Not connected!");
+ }
+ }
+
+ private void throwIfCalledByNotTrustedUidLocked() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID
+ && callingUid != 0 /*root*/) {
+ throw new SecurityException("Calling from not trusted UID!");
+ }
+ }
+}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 9c0064e..3342068 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -709,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...");
- WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
+ WindowManagerGlobal.getWindowSession().setWallpaperPosition(
windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep);
//Log.v(TAG, "...app returning after sending offsets!");
} catch (RemoteException e) {
@@ -747,7 +747,7 @@ public class WallpaperManager {
int x, int y, int z, Bundle extras) {
try {
//Log.v(TAG, "Sending new wallpaper offsets from app...");
- WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand(
+ WindowManagerGlobal.getWindowSession().sendWallpaperCommand(
windowToken, action, x, y, z, extras, false);
//Log.v(TAG, "...app returning after sending offsets!");
} catch (RemoteException e) {
@@ -767,7 +767,7 @@ public class WallpaperManager {
*/
public void clearWallpaperOffsets(IBinder windowToken) {
try {
- WindowManagerGlobal.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
+ WindowManagerGlobal.getWindowSession().setWallpaperPosition(
windowToken, -1, -1, -1, -1);
} catch (RemoteException e) {
// Ignore.
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4c0eba0..17e8dd9 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1513,4 +1513,72 @@ public class DevicePolicyManager {
}
}
}
+
+ /**
+ * @hide
+ * Sets the given package as the device owner. The package must already be installed and there
+ * shouldn't be an existing device owner registered, for this call to succeed. Also, this
+ * method must be called before the device is provisioned.
+ * @param packageName the package name of the application to be registered as the device owner.
+ * @return whether the package was successfully registered as the device owner.
+ * @throws IllegalArgumentException if the package name is null or invalid
+ * @throws IllegalStateException if a device owner is already registered or the device has
+ * already been provisioned.
+ */
+ public boolean setDeviceOwner(String packageName) throws IllegalArgumentException,
+ IllegalStateException {
+ if (mService != null) {
+ try {
+ return mService.setDeviceOwner(packageName);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to set device owner");
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Used to determine if a particular package has been registered as a Device Owner app.
+ * A device owner app is a special device admin that cannot be deactivated by the user, once
+ * activated as a device admin. It also cannot be uninstalled. To check if a particular
+ * package is currently registered as the device owner app, pass in the package name from
+ * {@link Context#getPackageName()} to this method.<p/>This is useful for device
+ * admin apps that want to check if they are also registered as the device owner app. The
+ * exact mechanism by which a device admin app is registered as a device owner app is defined by
+ * the setup process.
+ * @param packageName the package name of the app, to compare with the registered device owner
+ * app, if any.
+ * @return whether or not the package is registered as the device owner app.
+ */
+ public boolean isDeviceOwnerApp(String packageName) {
+ if (mService != null) {
+ try {
+ return mService.isDeviceOwner(packageName);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to check device owner");
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @hide
+ * Redirect to isDeviceOwnerApp.
+ */
+ public boolean isDeviceOwner(String packageName) {
+ return isDeviceOwnerApp(packageName);
+ }
+
+ /** @hide */
+ public String getDeviceOwner() {
+ if (mService != null) {
+ try {
+ return mService.getDeviceOwner();
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to get device owner");
+ }
+ }
+ return null;
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index e061ab3..b2a65bf 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -97,4 +97,8 @@ interface IDevicePolicyManager {
int numbers, int symbols, int nonletter, int userHandle);
void reportFailedPasswordAttempt(int userHandle);
void reportSuccessfulPasswordAttempt(int userHandle);
+
+ boolean setDeviceOwner(String packageName);
+ boolean isDeviceOwner(String packageName);
+ String getDeviceOwner();
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 0e835ed..67c772b 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -17,13 +17,17 @@
package android.app.backup;
import android.app.IBackupAgent;
+import android.app.QueuedWork;
import android.app.backup.IBackupManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
@@ -32,6 +36,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedList;
+import java.util.concurrent.CountDownLatch;
import libcore.io.ErrnoException;
import libcore.io.Libcore;
@@ -121,6 +126,32 @@ public abstract class BackupAgent extends ContextWrapper {
/** @hide */
public static final int TYPE_SYMLINK = 3;
+ Handler mHandler = null;
+
+ class SharedPrefsSynchronizer implements Runnable {
+ public final CountDownLatch mLatch = new CountDownLatch(1);
+
+ @Override
+ public void run() {
+ QueuedWork.waitToFinish();
+ mLatch.countDown();
+ }
+ };
+
+ // Syncing shared preferences deferred writes needs to happen on the main looper thread
+ private void waitForSharedPrefs() {
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ final SharedPrefsSynchronizer s = new SharedPrefsSynchronizer();
+ mHandler.postAtFrontOfQueue(s);
+ try {
+ s.mLatch.await();
+ } catch (InterruptedException e) { /* ignored */ }
+ }
+
+
public BackupAgent() {
super(null);
}
@@ -254,6 +285,21 @@ public abstract class BackupAgent extends ContextWrapper {
filterSet.add(databaseDir);
filterSet.remove(sharedPrefsDir);
fullBackupFileTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, sharedPrefsDir, filterSet, data);
+
+ // getExternalFilesDir() location associated with this app. Technically there should
+ // not be any files here if the app does not properly have permission to access
+ // external storage, but edge cases happen. fullBackupFileTree() catches
+ // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and
+ // we know a priori that processes running as the system UID are not permitted to
+ // access external storage, so we check for that as well to avoid nastygrams in
+ // the log.
+ if (Process.myUid() != Process.SYSTEM_UID) {
+ File efLocation = getExternalFilesDir(null);
+ if (efLocation != null) {
+ fullBackupFileTree(packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN,
+ efLocation.getCanonicalPath(), null, data);
+ }
+ }
}
/**
@@ -274,6 +320,7 @@ public abstract class BackupAgent extends ContextWrapper {
String spDir;
String cacheDir;
String libDir;
+ String efDir = null;
String filePath;
ApplicationInfo appInfo = getApplicationInfo();
@@ -288,6 +335,14 @@ public abstract class BackupAgent extends ContextWrapper {
? null
: new File(appInfo.nativeLibraryDir).getCanonicalPath();
+ // may or may not have external files access to attempt backup/restore there
+ if (Process.myUid() != Process.SYSTEM_UID) {
+ File efLocation = getExternalFilesDir(null);
+ if (efLocation != null) {
+ efDir = efLocation.getCanonicalPath();
+ }
+ }
+
// Now figure out which well-defined tree the file is placed in, working from
// most to least specific. We also specifically exclude the lib and cache dirs.
filePath = file.getCanonicalPath();
@@ -315,6 +370,9 @@ public abstract class BackupAgent extends ContextWrapper {
} else if (filePath.startsWith(mainDir)) {
domain = FullBackup.ROOT_TREE_TOKEN;
rootpath = mainDir;
+ } else if ((efDir != null) && filePath.startsWith(efDir)) {
+ domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
+ rootpath = efDir;
} else {
Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
return;
@@ -438,6 +496,15 @@ public abstract class BackupAgent extends ContextWrapper {
basePath = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
} else if (domain.equals(FullBackup.CACHE_TREE_TOKEN)) {
basePath = getCacheDir().getCanonicalPath();
+ } else if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
+ // make sure we can try to restore here before proceeding
+ if (Process.myUid() != Process.SYSTEM_UID) {
+ File efLocation = getExternalFilesDir(null);
+ if (efLocation != null) {
+ basePath = getExternalFilesDir(null).getCanonicalPath();
+ mode = -1; // < 0 is a token to skip attempting a chmod()
+ }
+ }
} else {
// Not a supported location
Log.i(TAG, "Unrecognized domain " + domain);
@@ -505,6 +572,11 @@ public abstract class BackupAgent extends ContextWrapper {
Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw ex;
} finally {
+ // Ensure that any SharedPreferences writes have landed after the backup,
+ // in case the app code has side effects (since apps cannot provide this
+ // guarantee themselves).
+ waitForSharedPrefs();
+
Binder.restoreCallingIdentity(ident);
try {
callbackBinder.opComplete(token);
@@ -532,6 +604,9 @@ public abstract class BackupAgent extends ContextWrapper {
Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw ex;
} finally {
+ // Ensure that any side-effect SharedPreferences writes have landed
+ waitForSharedPrefs();
+
Binder.restoreCallingIdentity(ident);
try {
callbackBinder.opComplete(token);
@@ -549,6 +624,10 @@ public abstract class BackupAgent extends ContextWrapper {
if (DEBUG) Log.v(TAG, "doFullBackup() invoked");
+ // Ensure that any SharedPreferences writes have landed *before*
+ // we potentially try to back up the underlying files directly.
+ waitForSharedPrefs();
+
try {
BackupAgent.this.onFullBackup(new FullBackupDataOutput(data));
} catch (IOException ex) {
@@ -558,6 +637,9 @@ public abstract class BackupAgent extends ContextWrapper {
Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw ex;
} finally {
+ // ... and then again after, as in the doBackup() case
+ waitForSharedPrefs();
+
// Send the EOD marker indicating that there is no more data
// forthcoming from this agent.
try {
@@ -587,6 +669,9 @@ public abstract class BackupAgent extends ContextWrapper {
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
+ // Ensure that any side-effect SharedPreferences writes have landed
+ waitForSharedPrefs();
+
Binder.restoreCallingIdentity(ident);
try {
callbackBinder.opComplete(token);
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index f859599..cb0737e 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -46,6 +46,7 @@ public class FullBackup {
public static final String DATA_TREE_TOKEN = "f";
public static final String DATABASE_TREE_TOKEN = "db";
public static final String SHAREDPREFS_TREE_TOKEN = "sp";
+ public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
public static final String CACHE_TREE_TOKEN = "c";
public static final String SHARED_STORAGE_TOKEN = "shared";
@@ -88,7 +89,7 @@ public class FullBackup {
* last modification time of the output file. if the {@code mode} parameter is
* negative then this parameter will be ignored.
* @param outFile Location within the filesystem to place the data. This must point
- * to a location that is writeable by the caller, prefereably using an absolute path.
+ * to a location that is writeable by the caller, preferably using an absolute path.
* @throws IOException
*/
static public void restoreFile(ParcelFileDescriptor data,
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index acdd0b5..bb4f5f1 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -152,6 +152,8 @@ interface IBackupManager {
* @param fd The file descriptor to which a 'tar' file stream is to be written
* @param includeApks If <code>true</code>, the resulting tar stream will include the
* application .apk files themselves as well as their data.
+ * @param includeObbs If <code>true</code>, the resulting tar stream will include any
+ * application expansion (OBB) files themselves belonging to each application.
* @param includeShared If <code>true</code>, the resulting tar stream will include
* the contents of the device's shared storage (SD card or equivalent).
* @param allApps If <code>true</code>, the resulting tar stream will include all
@@ -164,8 +166,9 @@ interface IBackupManager {
* @param packageNames The package names of the apps whose data (and optionally .apk files)
* are to be backed up. The <code>allApps</code> parameter supersedes this.
*/
- void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeShared,
- boolean allApps, boolean allIncludesSystem, in String[] packageNames);
+ void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
+ boolean includeShared, boolean allApps, boolean allIncludesSystem,
+ in String[] packageNames);
/**
* Restore device content from the data stream passed through the given socket. The
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index fa3bf4d..8aae45a 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -31,6 +31,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.TypedValue;
import android.widget.RemoteViews;
import android.widget.RemoteViews.OnClickHandler;
@@ -55,38 +56,39 @@ public class AppWidgetHost {
Context mContext;
String mPackageName;
+ Handler mHandler;
+ int mHostId;
+ Callbacks mCallbacks = new Callbacks();
+ final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>();
+ private OnClickHandler mOnClickHandler;
class Callbacks extends IAppWidgetHost.Stub {
- public void updateAppWidget(int appWidgetId, RemoteViews views) {
+ public void updateAppWidget(int appWidgetId, RemoteViews views, int userId) {
if (isLocalBinder() && views != null) {
views = views.clone();
- views.setUser(mUser);
+ views.setUser(new UserHandle(userId));
}
- Message msg = mHandler.obtainMessage(HANDLE_UPDATE);
- msg.arg1 = appWidgetId;
- msg.obj = views;
+ Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, userId, views);
msg.sendToTarget();
}
- public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
+ public void providerChanged(int appWidgetId, AppWidgetProviderInfo info, int userId) {
if (isLocalBinder() && info != null) {
info = info.clone();
}
- Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED);
- msg.arg1 = appWidgetId;
- msg.obj = info;
+ Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED,
+ appWidgetId, userId, info);
msg.sendToTarget();
}
- public void providersChanged() {
- Message msg = mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED);
+ public void providersChanged(int userId) {
+ Message msg = mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED, userId, 0);
msg.sendToTarget();
}
- public void viewDataChanged(int appWidgetId, int viewId) {
- Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED);
- msg.arg1 = appWidgetId;
- msg.arg2 = viewId;
+ public void viewDataChanged(int appWidgetId, int viewId, int userId) {
+ Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
+ appWidgetId, viewId, userId);
msg.sendToTarget();
}
}
@@ -99,7 +101,7 @@ public class AppWidgetHost {
public void handleMessage(Message msg) {
switch (msg.what) {
case HANDLE_UPDATE: {
- updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
+ updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj, msg.arg2);
break;
}
case HANDLE_PROVIDER_CHANGED: {
@@ -111,22 +113,13 @@ public class AppWidgetHost {
break;
}
case HANDLE_VIEW_DATA_CHANGED: {
- viewDataChanged(msg.arg1, msg.arg2);
+ viewDataChanged(msg.arg1, msg.arg2, (Integer) msg.obj);
break;
}
}
}
}
- Handler mHandler;
-
- int mHostId;
- Callbacks mCallbacks = new Callbacks();
- final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>();
- private OnClickHandler mOnClickHandler;
- // Optionally set by lockscreen
- private UserHandle mUser;
-
public AppWidgetHost(Context context, int hostId) {
this(context, hostId, null, context.getMainLooper());
}
@@ -140,14 +133,9 @@ public class AppWidgetHost {
mOnClickHandler = handler;
mHandler = new UpdateHandler(looper);
mDisplayMetrics = context.getResources().getDisplayMetrics();
- mUser = Process.myUserHandle();
bindService();
}
- /** @hide */
- public void setUserId(int userId) {
- mUser = new UserHandle(userId);
- }
private static void bindService() {
synchronized (sServiceLock) {
@@ -163,23 +151,15 @@ public class AppWidgetHost {
* becomes visible, i.e. from onStart() in your Activity.
*/
public void startListening() {
- startListeningAsUser(UserHandle.myUserId());
- }
-
- /**
- * Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity
- * becomes visible, i.e. from onStart() in your Activity.
- * @hide
- */
- public void startListeningAsUser(int userId) {
int[] updatedIds;
ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
+ final int userId = mContext.getUserId();
try {
if (mPackageName == null) {
mPackageName = mContext.getPackageName();
}
- updatedIds = sService.startListeningAsUser(
+ updatedIds = sService.startListening(
mCallbacks, mPackageName, mHostId, updatedViews, userId);
}
catch (RemoteException e) {
@@ -191,7 +171,7 @@ public class AppWidgetHost {
if (updatedViews.get(i) != null) {
updatedViews.get(i).setUser(new UserHandle(userId));
}
- updateAppWidgetView(updatedIds[i], updatedViews.get(i));
+ updateAppWidgetView(updatedIds[i], updatedViews.get(i), userId);
}
}
@@ -201,26 +181,14 @@ public class AppWidgetHost {
*/
public void stopListening() {
try {
- sService.stopListeningAsUser(mHostId, UserHandle.myUserId());
+ sService.stopListening(mHostId, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
- }
- /**
- * Stop receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity is
- * no longer visible, i.e. from onStop() in your Activity.
- * @hide
- */
- public void stopListeningAsUser(int userId) {
- try {
- sService.stopListeningAsUser(mHostId, userId);
- }
- catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
- // Also clear the views
+ // This is here because keyguard needs it since it'll be switching users after this call.
+ // If it turns out other apps need to call this often, we should re-think how this works.
clearViews();
}
@@ -230,11 +198,12 @@ public class AppWidgetHost {
* @return a appWidgetId
*/
public int allocateAppWidgetId() {
+
try {
if (mPackageName == null) {
mPackageName = mContext.getPackageName();
}
- return sService.allocateAppWidgetId(mPackageName, mHostId);
+ return sService.allocateAppWidgetId(mPackageName, mHostId, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -247,7 +216,7 @@ public class AppWidgetHost {
* @return a appWidgetId
* @hide
*/
- public static int allocateAppWidgetIdForSystem(int hostId) {
+ public static int allocateAppWidgetIdForSystem(int hostId, int userId) {
checkCallerIsSystem();
try {
if (sService == null) {
@@ -256,7 +225,7 @@ public class AppWidgetHost {
Context systemContext =
(Context) ActivityThread.currentActivityThread().getSystemContext();
String packageName = systemContext.getPackageName();
- return sService.allocateAppWidgetId(packageName, hostId);
+ return sService.allocateAppWidgetId(packageName, hostId, userId);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
@@ -272,7 +241,7 @@ public class AppWidgetHost {
if (sService == null) {
bindService();
}
- return sService.getAppWidgetIdsForHost(mHostId);
+ return sService.getAppWidgetIdsForHost(mHostId, mContext.getUserId());
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
@@ -297,7 +266,7 @@ public class AppWidgetHost {
synchronized (mViews) {
mViews.remove(appWidgetId);
try {
- sService.deleteAppWidgetId(appWidgetId);
+ sService.deleteAppWidgetId(appWidgetId, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -309,13 +278,13 @@ public class AppWidgetHost {
* Stop listening to changes for this AppWidget.
* @hide
*/
- public static void deleteAppWidgetIdForSystem(int appWidgetId) {
+ public static void deleteAppWidgetIdForSystem(int appWidgetId, int userId) {
checkCallerIsSystem();
try {
if (sService == null) {
bindService();
}
- sService.deleteAppWidgetId(appWidgetId);
+ sService.deleteAppWidgetId(appWidgetId, userId);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
@@ -331,7 +300,7 @@ public class AppWidgetHost {
*/
public void deleteHost() {
try {
- sService.deleteHost(mHostId);
+ sService.deleteHost(mHostId, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -347,8 +316,16 @@ public class AppWidgetHost {
* </ul>
*/
public static void deleteAllHosts() {
+ deleteAllHosts(UserHandle.myUserId());
+ }
+
+ /**
+ * Private method containing a userId
+ * @hide
+ */
+ public static void deleteAllHosts(int userId) {
try {
- sService.deleteAllHosts();
+ sService.deleteAllHosts(userId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -361,8 +338,9 @@ public class AppWidgetHost {
*/
public final AppWidgetHostView createView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
- AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
- view.setUserId(mUser.getIdentifier());
+ final int userId = mContext.getUserId();
+ AppWidgetHostView view = onCreateView(mContext, appWidgetId, appWidget);
+ view.setUserId(userId);
view.setOnClickHandler(mOnClickHandler);
view.setAppWidget(appWidgetId, appWidget);
synchronized (mViews) {
@@ -370,9 +348,9 @@ public class AppWidgetHost {
}
RemoteViews views;
try {
- views = sService.getAppWidgetViews(appWidgetId);
+ views = sService.getAppWidgetViews(appWidgetId, userId);
if (views != null) {
- views.setUser(mUser);
+ views.setUser(new UserHandle(mContext.getUserId()));
}
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -422,10 +400,10 @@ public class AppWidgetHost {
* are added, updated or removed, or widget components are enabled or disabled.)
*/
protected void onProvidersChanged() {
- // Do nothing
+ // Does nothing
}
- void updateAppWidgetView(int appWidgetId, RemoteViews views) {
+ void updateAppWidgetView(int appWidgetId, RemoteViews views, int userId) {
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
@@ -435,7 +413,7 @@ public class AppWidgetHost {
}
}
- void viewDataChanged(int appWidgetId, int viewId) {
+ void viewDataChanged(int appWidgetId, int viewId, int userId) {
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 6b1c3e2..d1c7bec 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -16,6 +16,7 @@
package android.appwidget;
+import android.app.ActivityManagerNative;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -48,8 +49,8 @@ public class AppWidgetManager {
static final String TAG = "AppWidgetManager";
/**
- * Send this from your {@link AppWidgetHost} activity when you want to pick an AppWidget to display.
- * The AppWidget picker activity will be launched.
+ * Activity action to launch from your {@link AppWidgetHost} activity when you want to
+ * pick an AppWidget to display. The AppWidget picker activity will be launched.
* <p>
* You must supply the following extras:
* <table>
@@ -88,8 +89,8 @@ public class AppWidgetManager {
ACTION_KEYGUARD_APPWIDGET_PICK = "android.appwidget.action.KEYGUARD_APPWIDGET_PICK";
/**
- * Send this from your {@link AppWidgetHost} activity when you want to bind an AppWidget to
- * display and bindAppWidgetIdIfAllowed returns false.
+ * Activity action to launch from your {@link AppWidgetHost} activity when you want to bind
+ * an AppWidget to display and bindAppWidgetIdIfAllowed returns false.
* <p>
* You must supply the following extras:
* <table>
@@ -268,8 +269,11 @@ public class AppWidgetManager {
/**
* Sent when the custom extras for an AppWidget change.
*
- * @see AppWidgetProvider#onAppWidgetOptionsChanged
- * AppWidgetProvider.onAppWidgetOptionsChanged(Context context,
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @see AppWidgetProvider#onAppWidgetOptionsChanged
+ * AppWidgetProvider.onAppWidgetOptionsChanged(Context context,
* AppWidgetManager appWidgetManager, int appWidgetId, Bundle newExtras)
*/
public static final String ACTION_APPWIDGET_OPTIONS_CHANGED = "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS";
@@ -277,6 +281,9 @@ public class AppWidgetManager {
/**
* Sent when an instance of an AppWidget is deleted from its host.
*
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
* @see AppWidgetProvider#onDeleted AppWidgetProvider.onDeleted(Context context, int[] appWidgetIds)
*/
public static final String ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED";
@@ -284,6 +291,9 @@ public class AppWidgetManager {
/**
* Sent when an instance of an AppWidget is removed from the last host.
*
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
* @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context)
*/
public static final String ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED";
@@ -293,6 +303,9 @@ public class AppWidgetManager {
* This broadcast is sent at boot time if there is a AppWidgetHost installed with
* an instance for this provider.
*
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
* @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context)
*/
public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED";
@@ -352,7 +365,7 @@ public class AppWidgetManager {
* It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
* and outside of the handler.
* This method will only work when called from the uid that owns the AppWidget provider.
- *
+ *
* <p>
* The total Bitmap memory used by the RemoteViews object cannot exceed that required to
* fill the screen 1.5 times, ie. (screen width x screen height x 4 x 1.5) bytes.
@@ -362,7 +375,7 @@ public class AppWidgetManager {
*/
public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
try {
- sService.updateAppWidgetIds(appWidgetIds, views);
+ sService.updateAppWidgetIds(appWidgetIds, views, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -382,7 +395,7 @@ public class AppWidgetManager {
*/
public void updateAppWidgetOptions(int appWidgetId, Bundle options) {
try {
- sService.updateAppWidgetOptions(appWidgetId, options);
+ sService.updateAppWidgetOptions(appWidgetId, options, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -402,7 +415,7 @@ public class AppWidgetManager {
*/
public Bundle getAppWidgetOptions(int appWidgetId) {
try {
- return sService.getAppWidgetOptions(appWidgetId);
+ return sService.getAppWidgetOptions(appWidgetId, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -436,7 +449,7 @@ public class AppWidgetManager {
* Perform an incremental update or command on the widget(s) specified by appWidgetIds.
*
* 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
+ * RemoteViews object which is passed is understood to be an incomplete representation of the
* 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.
@@ -458,7 +471,7 @@ public class AppWidgetManager {
*/
public void partiallyUpdateAppWidget(int[] appWidgetIds, RemoteViews views) {
try {
- sService.partiallyUpdateAppWidgetIds(appWidgetIds, views);
+ sService.partiallyUpdateAppWidgetIds(appWidgetIds, views, mContext.getUserId());
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
@@ -507,7 +520,7 @@ public class AppWidgetManager {
*/
public void updateAppWidget(ComponentName provider, RemoteViews views) {
try {
- sService.updateAppWidgetProvider(provider, views);
+ sService.updateAppWidgetProvider(provider, views, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -516,14 +529,14 @@ public class AppWidgetManager {
/**
* Notifies the specified collection view in all the specified AppWidget instances
- * to invalidate their currently data.
+ * to invalidate their data.
*
- * @param appWidgetIds The AppWidget instances for which to notify of view data changes.
+ * @param appWidgetIds The AppWidget instances to notify of view data changes.
* @param viewId The collection view id.
*/
public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
try {
- sService.notifyAppWidgetViewDataChanged(appWidgetIds, viewId);
+ sService.notifyAppWidgetViewDataChanged(appWidgetIds, viewId, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -531,11 +544,11 @@ public class AppWidgetManager {
}
/**
- * Notifies the specified collection view in all the specified AppWidget instance
- * to invalidate it's currently data.
+ * Notifies the specified collection view in the specified AppWidget instance
+ * to invalidate its data.
*
- * @param appWidgetId The AppWidget instance for which to notify of view data changes.
- * @param viewId The collection view id.
+ * @param appWidgetId The AppWidget instance to notify of view data changes.
+ * @param viewId The collection view id.
*/
public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
notifyAppWidgetViewDataChanged(new int[] { appWidgetId }, viewId);
@@ -557,7 +570,8 @@ public class AppWidgetManager {
*/
public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter) {
try {
- List<AppWidgetProviderInfo> providers = sService.getInstalledProviders(categoryFilter);
+ List<AppWidgetProviderInfo> providers = sService.getInstalledProviders(categoryFilter,
+ mContext.getUserId());
for (AppWidgetProviderInfo info : providers) {
// Converting complex to dp.
info.minWidth =
@@ -584,7 +598,8 @@ public class AppWidgetManager {
*/
public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
try {
- AppWidgetProviderInfo info = sService.getAppWidgetInfo(appWidgetId);
+ AppWidgetProviderInfo info = sService.getAppWidgetInfo(appWidgetId,
+ mContext.getUserId());
if (info != null) {
// Converting complex to dp.
info.minWidth =
@@ -617,7 +632,7 @@ public class AppWidgetManager {
*/
public void bindAppWidgetId(int appWidgetId, ComponentName provider) {
try {
- sService.bindAppWidgetId(appWidgetId, provider, null);
+ sService.bindAppWidgetId(appWidgetId, provider, null, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -641,7 +656,7 @@ public class AppWidgetManager {
*/
public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) {
try {
- sService.bindAppWidgetId(appWidgetId, provider, options);
+ sService.bindAppWidgetId(appWidgetId, provider, options, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -667,7 +682,7 @@ public class AppWidgetManager {
}
try {
return sService.bindAppWidgetIdIfAllowed(
- mContext.getPackageName(), appWidgetId, provider, null);
+ mContext.getPackageName(), appWidgetId, provider, null, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -696,8 +711,8 @@ public class AppWidgetManager {
return false;
}
try {
- return sService.bindAppWidgetIdIfAllowed(
- mContext.getPackageName(), appWidgetId, provider, options);
+ return sService.bindAppWidgetIdIfAllowed(mContext.getPackageName(), appWidgetId,
+ provider, options, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -715,7 +730,7 @@ public class AppWidgetManager {
*/
public boolean hasBindAppWidgetPermission(String packageName) {
try {
- return sService.hasBindAppWidgetPermission(packageName);
+ return sService.hasBindAppWidgetPermission(packageName, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -733,7 +748,7 @@ public class AppWidgetManager {
*/
public void setBindAppWidgetPermission(String packageName, boolean permission) {
try {
- sService.setBindAppWidgetPermission(packageName, permission);
+ sService.setBindAppWidgetPermission(packageName, permission, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -794,7 +809,7 @@ public class AppWidgetManager {
*/
public int[] getAppWidgetIds(ComponentName provider) {
try {
- return sService.getAppWidgetIds(provider);
+ return sService.getAppWidgetIds(provider, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 6367e16..79bb476 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -20,9 +20,7 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
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.ParcelUuid;
import android.os.RemoteException;
@@ -31,11 +29,14 @@ import android.util.Log;
import android.util.Pair;
import java.io.IOException;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.HashMap;
import java.util.LinkedList;
+import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
@@ -46,17 +47,22 @@ import java.util.UUID;
* device discovery, query a list of bonded (paired) devices,
* instantiate a {@link BluetoothDevice} using a known MAC address, and create
* a {@link BluetoothServerSocket} to listen for connection requests from other
- * devices.
+ * devices, and start a scan for Bluetooth LE devices.
*
* <p>To get a {@link BluetoothAdapter} representing the local Bluetooth
- * adapter, call the static {@link #getDefaultAdapter} method.
+ * adapter, when running on JELLY_BEAN_MR1 and below, call the
+ * static {@link #getDefaultAdapter} method; when running on JELLY_BEAN_MR2 and
+ * higher, retrieve it through
+ * {@link android.content.Context#getSystemService} with
+ * {@link android.content.Context#BLUETOOTH_SERVICE}.
* Fundamentally, this is your starting point for all
* Bluetooth actions. Once you have the local adapter, you can get a set of
* {@link BluetoothDevice} objects representing all paired devices with
* {@link #getBondedDevices()}; start device discovery with
* {@link #startDiscovery()}; or create a {@link BluetoothServerSocket} to
* listen for incoming connection requests with
- * {@link #listenUsingRfcommWithServiceRecord(String,UUID)}.
+ * {@link #listenUsingRfcommWithServiceRecord(String,UUID)}; or start a scan for
+ * Bluetooth LE devices with {@link #startLeScan(LeScanCallback callback)}.
*
* <p class="note"><strong>Note:</strong>
* Most methods require the {@link android.Manifest.permission#BLUETOOTH}
@@ -358,7 +364,7 @@ public final class BluetoothAdapter {
private final IBluetoothManager mManagerService;
private IBluetooth mService;
- private Handler mServiceRecordHandler;
+ private final Map<LeScanCallback, GattCallbackWrapper> mLeScanClients;
/**
* Get a handle to the default local Bluetooth adapter.
@@ -393,7 +399,7 @@ public final class BluetoothAdapter {
mService = managerService.registerAdapter(mManagerCallback);
} catch (RemoteException e) {Log.e(TAG, "", e);}
mManagerService = managerService;
- mServiceRecordHandler = null;
+ mLeScanClients = new HashMap<LeScanCallback, GattCallbackWrapper>();
}
/**
@@ -1136,8 +1142,9 @@ public final class BluetoothAdapter {
/**
* Get the profile proxy object associated with the profile.
*
- * <p>Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET} or
- * {@link BluetoothProfile#A2DP}. Clients must implements
+ * <p>Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET},
+ * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, or
+ * {@link BluetoothProfile#GATT_SERVER}. Clients must implement
* {@link BluetoothProfile.ServiceListener} to get notified of
* the connection status and to get the proxy object.
*
@@ -1206,6 +1213,14 @@ public final class BluetoothAdapter {
BluetoothHealth health = (BluetoothHealth)proxy;
health.close();
break;
+ case BluetoothProfile.GATT:
+ BluetoothGatt gatt = (BluetoothGatt)proxy;
+ gatt.close();
+ break;
+ case BluetoothProfile.GATT_SERVER:
+ BluetoothGattServer gattServer = (BluetoothGattServer)proxy;
+ gattServer.close();
+ break;
}
}
@@ -1397,4 +1412,326 @@ public final class BluetoothAdapter {
mProxyServiceStateCallbacks.remove(cb);
}
}
+
+ /**
+ * Callback interface used to deliver LE scan results.
+ *
+ * @see #startLeScan(LeScanCallback)
+ * @see #startLeScan(UUID[], LeScanCallback)
+ */
+ public interface LeScanCallback {
+ /**
+ * Callback reporting an LE device found during a device scan initiated
+ * by the {@link BluetoothAdapter#startLeScan} function.
+ *
+ * @param device Identifies the remote device
+ * @param rssi The RSSI value for the remote device as reported by the
+ * Bluetooth hardware. 0 if no RSSI value is available.
+ * @param scanRecord The content of the advertisement record offered by
+ * the remote device.
+ */
+ public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord);
+ }
+
+ /**
+ * Starts a scan for Bluetooth LE devices.
+ *
+ * <p>Results of the scan are reported using the
+ * {@link LeScanCallback#onLeScan} callback.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param callback the callback LE scan results are delivered
+ * @return true, if the scan was started successfully
+ */
+ public boolean startLeScan(LeScanCallback callback) {
+ return startLeScan(null, callback);
+ }
+
+ /**
+ * Starts a scan for Bluetooth LE devices, looking for devices that
+ * advertise given services.
+ *
+ * <p>Devices which advertise all specified services are reported using the
+ * {@link LeScanCallback#onLeScan} callback.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param serviceUuids Array of services to look for
+ * @param callback the callback LE scan results are delivered
+ * @return true, if the scan was started successfully
+ */
+ public boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) {
+ if (DBG) Log.d(TAG, "startLeScan(): " + serviceUuids);
+
+ if (callback == null) {
+ if (DBG) Log.e(TAG, "startLeScan: null callback");
+ return false;
+ }
+
+ synchronized(mLeScanClients) {
+ if (mLeScanClients.containsKey(callback)) {
+ if (DBG) Log.e(TAG, "LE Scan has already started");
+ return false;
+ }
+
+ try {
+ IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
+ if (iGatt == null) {
+ // BLE is not supported
+ return false;
+ }
+
+ UUID uuid = UUID.randomUUID();
+ GattCallbackWrapper wrapper = new GattCallbackWrapper(this, callback, serviceUuids);
+ iGatt.registerClient(new ParcelUuid(uuid), wrapper);
+ if (wrapper.scanStarted()) {
+ mLeScanClients.put(callback, wrapper);
+ return true;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Stops an ongoing Bluetooth LE device scan.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param callback used to identify which scan to stop
+ * must be the same handle used to start the scan
+ */
+ public void stopLeScan(LeScanCallback callback) {
+ if (DBG) Log.d(TAG, "stopLeScan()");
+ GattCallbackWrapper wrapper;
+ synchronized(mLeScanClients) {
+ wrapper = mLeScanClients.remove(callback);
+ if (wrapper == null) return;
+ }
+ wrapper.stopLeScan();
+ }
+
+ /**
+ * Bluetooth GATT interface callbacks
+ */
+ private static class GattCallbackWrapper extends IBluetoothGattCallback.Stub {
+ private static final int LE_CALLBACK_REG_TIMEOUT = 2000;
+ private static final int LE_CALLBACK_REG_WAIT_COUNT = 5;
+
+ private final LeScanCallback mLeScanCb;
+ // mLeHandle 0: not registered
+ // -1: scan stopped
+ // >0: registered and scan started
+ private int mLeHandle;
+ private final UUID[] mScanFilter;
+ private WeakReference<BluetoothAdapter> mBluetoothAdapter;
+
+ public GattCallbackWrapper(BluetoothAdapter bluetoothAdapter,
+ LeScanCallback leScanCb, UUID[] uuid) {
+ mBluetoothAdapter = new WeakReference<BluetoothAdapter>(bluetoothAdapter);
+ mLeScanCb = leScanCb;
+ mScanFilter = uuid;
+ mLeHandle = 0;
+ }
+
+ public boolean scanStarted() {
+ boolean started = false;
+ synchronized(this) {
+ if (mLeHandle == -1) return false;
+
+ int count = 0;
+ // wait for callback registration and LE scan to start
+ while (mLeHandle == 0 && count < LE_CALLBACK_REG_WAIT_COUNT) {
+ try {
+ wait(LE_CALLBACK_REG_TIMEOUT);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Callback reg wait interrupted: " + e);
+ }
+ count++;
+ }
+ started = (mLeHandle > 0);
+ }
+ return started;
+ }
+
+ public void stopLeScan() {
+ synchronized(this) {
+ if (mLeHandle <= 0) {
+ Log.e(TAG, "Error state, mLeHandle: " + mLeHandle);
+ return;
+ }
+ BluetoothAdapter adapter = mBluetoothAdapter.get();
+ if (adapter != null) {
+ try {
+ IBluetoothGatt iGatt = adapter.getBluetoothManager().getBluetoothGatt();
+ iGatt.stopScan(mLeHandle, false);
+ iGatt.unregisterClient(mLeHandle);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to stop scan and unregister" + e);
+ }
+ } else {
+ Log.e(TAG, "stopLeScan, BluetoothAdapter is null");
+ }
+ mLeHandle = -1;
+ notifyAll();
+ }
+ }
+
+ /**
+ * Application interface registered - app is ready to go
+ */
+ public void onClientRegistered(int status, int clientIf) {
+ if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status +
+ " clientIf=" + clientIf);
+ synchronized(this) {
+ if (mLeHandle == -1) {
+ if (DBG) Log.d(TAG, "onClientRegistered LE scan canceled");
+ }
+
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ mLeHandle = clientIf;
+ IBluetoothGatt iGatt = null;
+ try {
+ BluetoothAdapter adapter = mBluetoothAdapter.get();
+ if (adapter != null) {
+ iGatt = adapter.getBluetoothManager().getBluetoothGatt();
+ if (mScanFilter == null) {
+ iGatt.startScan(mLeHandle, false);
+ } else {
+ ParcelUuid[] uuids = new ParcelUuid[mScanFilter.length];
+ for(int i = 0; i != uuids.length; ++i) {
+ uuids[i] = new ParcelUuid(mScanFilter[i]);
+ }
+ iGatt.startScanWithUuids(mLeHandle, false, uuids);
+ }
+ } else {
+ Log.e(TAG, "onClientRegistered, BluetoothAdapter null");
+ mLeHandle = -1;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "fail to start le scan: " + e);
+ mLeHandle = -1;
+ }
+ if (mLeHandle == -1) {
+ // registration succeeded but start scan failed
+ if (iGatt != null) {
+ try {
+ iGatt.unregisterClient(mLeHandle);
+ } catch (RemoteException e) {
+ Log.e(TAG, "fail to unregister callback: " + mLeHandle +
+ " error: " + e);
+ }
+ }
+ }
+ } else {
+ // registration failed
+ mLeHandle = -1;
+ }
+ notifyAll();
+ }
+ }
+
+ public void onClientConnectionState(int status, int clientIf,
+ boolean connected, String address) {
+ // no op
+ }
+
+ /**
+ * Callback reporting an LE scan result.
+ * @hide
+ */
+ public void onScanResult(String address, int rssi, byte[] advData) {
+ if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
+
+ // Check null in case the scan has been stopped
+ synchronized(this) {
+ if (mLeHandle <= 0) return;
+ }
+ try {
+ BluetoothAdapter adapter = mBluetoothAdapter.get();
+ if (adapter == null) {
+ Log.d(TAG, "onScanResult, BluetoothAdapter null");
+ return;
+ }
+ mLeScanCb.onLeScan(adapter.getRemoteDevice(address), rssi, advData);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ public void onGetService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid) {
+ // no op
+ }
+
+ public void onGetIncludedService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int inclSrvcType, int inclSrvcInstId,
+ ParcelUuid inclSrvcUuid) {
+ // no op
+ }
+
+ public void onGetCharacteristic(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int charProps) {
+ // no op
+ }
+
+ public void onGetDescriptor(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ ParcelUuid descUuid) {
+ // no op
+ }
+
+ public void onSearchComplete(String address, int status) {
+ // no op
+ }
+
+ public void onCharacteristicRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid, byte[] value) {
+ // no op
+ }
+
+ public void onCharacteristicWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid) {
+ // no op
+ }
+
+ public void onNotify(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ byte[] value) {
+ // no op
+ }
+
+ public void onDescriptorRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ ParcelUuid descrUuid, byte[] value) {
+ // no op
+ }
+
+ public void onDescriptorWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ ParcelUuid descrUuid) {
+ // no op
+ }
+
+ public void onExecuteWrite(String address, int status) {
+ // no op
+ }
+
+ public void onReadRemoteRssi(String address, int rssi, int status) {
+ // no op
+ }
+ }
+
}
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 4cc22b4..3ee7142 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -18,6 +18,7 @@ package android.bluetooth;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.content.Context;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -106,7 +107,7 @@ public final class BluetoothDevice implements Parcelable {
* <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
* #EXTRA_CLASS}.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
- * @see {@link BluetoothClass}
+ * {@see BluetoothClass}
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CLASS_CHANGED =
@@ -261,6 +262,26 @@ public final class BluetoothDevice implements Parcelable {
public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY";
/**
+ * Bluetooth device type, Unknown
+ */
+ public static final int DEVICE_TYPE_UNKNOWN = 0;
+
+ /**
+ * Bluetooth device type, Classic - BR/EDR devices
+ */
+ public static final int DEVICE_TYPE_CLASSIC = 1;
+
+ /**
+ * Bluetooth device type, Low Energy - LE-only
+ */
+ public static final int DEVICE_TYPE_LE = 2;
+
+ /**
+ * Bluetooth device type, Dual Mode - BR/EDR/LE
+ */
+ public static final int DEVICE_TYPE_DUAL = 3;
+
+ /**
* Broadcast Action: This intent is used to broadcast the {@link UUID}
* wrapped as a {@link android.os.ParcelUuid} of the remote device after it
* has been fetched. This intent is sent only when the UUIDs of the remote
@@ -601,6 +622,26 @@ public final class BluetoothDevice implements Parcelable {
}
/**
+ * Get the Bluetooth device type of the remote device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @return the device type {@link #DEVICE_TYPE_CLASSIC}, {@link #DEVICE_TYPE_LE}
+ * {@link #DEVICE_TYPE_DUAL}.
+ * {@link #DEVICE_TYPE_UNKNOWN} if it's not available
+ */
+ public int getType() {
+ if (sService == null) {
+ Log.e(TAG, "BT not enabled. Cannot get Remote Device type");
+ return DEVICE_TYPE_UNKNOWN;
+ }
+ try {
+ return sService.getRemoteType(this);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return DEVICE_TYPE_UNKNOWN;
+ }
+
+ /**
* Get the Bluetooth alias of the remote device.
* <p>Alias is the locally modified name of a remote device.
*
@@ -1126,4 +1167,34 @@ public final class BluetoothDevice implements Parcelable {
return pinBytes;
}
+ /**
+ * Connect to GATT Server hosted by this device. Caller acts as GATT client.
+ * The callback is used to deliver results to Caller, such as connection status as well
+ * as any further GATT client operations.
+ * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
+ * GATT client operations.
+ * @param callback GATT callback handler that will receive asynchronous callbacks.
+ * @param autoConnect Whether to directly connect to the remote device (false)
+ * or to automatically connect as soon as the remote
+ * device becomes available (true).
+ * @throws IllegalArgumentException if callback is null
+ */
+ public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+ BluetoothGattCallback callback) {
+ // TODO(Bluetooth) check whether platform support BLE
+ // Do the check here or in GattServer?
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ IBluetoothManager managerService = adapter.getBluetoothManager();
+ try {
+ IBluetoothGatt iGatt = managerService.getBluetoothGatt();
+ if (iGatt == null) {
+ // BLE is not supported
+ return null;
+ }
+ BluetoothGatt gatt = new BluetoothGatt(context, iGatt, this);
+ gatt.connect(autoConnect, callback);
+ return gatt;
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return null;
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
new file mode 100644
index 0000000..0752df8
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -0,0 +1,1169 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothStateChangeCallback;
+
+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.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Public API for the Bluetooth GATT Profile.
+ *
+ * <p>This class provides Bluetooth GATT functionality to enable communication
+ * with Bluetooth Smart or Smart Ready devices.
+ *
+ * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallback}
+ * and call {@link BluetoothDevice#connectGatt} to get a instance of this class.
+ * GATT capable devices can be discovered using the Bluetooth device discovery or BLE
+ * scan process.
+ */
+public final class BluetoothGatt implements BluetoothProfile {
+ private static final String TAG = "BluetoothGatt";
+ private static final boolean DBG = true;
+ private static final boolean VDBG = true;
+
+ private final Context mContext;
+ private IBluetoothGatt mService;
+ private BluetoothGattCallback mCallback;
+ private int mClientIf;
+ private boolean mAuthRetry = false;
+ private BluetoothDevice mDevice;
+ private boolean mAutoConnect;
+ private int mConnState;
+ private final Object mStateLock = new Object();
+
+ private static final int CONN_STATE_IDLE = 0;
+ private static final int CONN_STATE_CONNECTING = 1;
+ private static final int CONN_STATE_CONNECTED = 2;
+ private static final int CONN_STATE_DISCONNECTING = 3;
+ private static final int CONN_STATE_CLOSED = 4;
+
+ private List<BluetoothGattService> mServices;
+
+ /** A GATT operation completed successfully */
+ public static final int GATT_SUCCESS = 0;
+
+ /** GATT read operation is not permitted */
+ public static final int GATT_READ_NOT_PERMITTED = 0x2;
+
+ /** GATT write operation is not permitted */
+ public static final int GATT_WRITE_NOT_PERMITTED = 0x3;
+
+ /** Insufficient authentication for a given operation */
+ public static final int GATT_INSUFFICIENT_AUTHENTICATION = 0x5;
+
+ /** The given request is not supported */
+ public static final int GATT_REQUEST_NOT_SUPPORTED = 0x6;
+
+ /** Insufficient encryption for a given operation */
+ public static final int GATT_INSUFFICIENT_ENCRYPTION = 0xf;
+
+ /** A read or write operation was requested with an invalid offset */
+ public static final int GATT_INVALID_OFFSET = 0x7;
+
+ /** A write operation exceeds the maximum length of the attribute */
+ public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd;
+
+ /** A GATT operation failed, errors other than the above */
+ public static final int GATT_FAILURE = 0x101;
+
+ /**
+ * No authentication required.
+ * @hide
+ */
+ /*package*/ static final int AUTHENTICATION_NONE = 0;
+
+ /**
+ * Authentication requested; no man-in-the-middle protection required.
+ * @hide
+ */
+ /*package*/ static final int AUTHENTICATION_NO_MITM = 1;
+
+ /**
+ * Authentication with man-in-the-middle protection requested.
+ * @hide
+ */
+ /*package*/ static final int AUTHENTICATION_MITM = 2;
+
+ /**
+ * Bluetooth GATT interface callbacks
+ */
+ private final IBluetoothGattCallback mBluetoothGattCallback =
+ new IBluetoothGattCallback.Stub() {
+ /**
+ * Application interface registered - app is ready to go
+ * @hide
+ */
+ public void onClientRegistered(int status, int clientIf) {
+ if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status
+ + " clientIf=" + clientIf);
+ if (VDBG) {
+ synchronized(mStateLock) {
+ if (mConnState != CONN_STATE_CONNECTING) {
+ Log.e(TAG, "Bad connection state: " + mConnState);
+ }
+ }
+ }
+ mClientIf = clientIf;
+ if (status != GATT_SUCCESS) {
+ mCallback.onConnectionStateChange(BluetoothGatt.this, GATT_FAILURE,
+ BluetoothProfile.STATE_DISCONNECTED);
+ synchronized(mStateLock) {
+ mConnState = CONN_STATE_IDLE;
+ }
+ return;
+ }
+ try {
+ mService.clientConnect(mClientIf, mDevice.getAddress(),
+ !mAutoConnect); // autoConnect is inverse of "isDirect"
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Client connection state changed
+ * @hide
+ */
+ public void onClientConnectionState(int status, int clientIf,
+ boolean connected, String address) {
+ if (DBG) Log.d(TAG, "onClientConnectionState() - status=" + status
+ + " clientIf=" + clientIf + " device=" + address);
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ int profileState = connected ? BluetoothProfile.STATE_CONNECTED :
+ BluetoothProfile.STATE_DISCONNECTED;
+ try {
+ mCallback.onConnectionStateChange(BluetoothGatt.this, status, profileState);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+
+ synchronized(mStateLock) {
+ if (connected) {
+ mConnState = CONN_STATE_CONNECTED;
+ } else {
+ mConnState = CONN_STATE_IDLE;
+ }
+ }
+ }
+
+ /**
+ * Callback reporting an LE scan result.
+ * @hide
+ */
+ public void onScanResult(String address, int rssi, byte[] advData) {
+ // no op
+ }
+
+ /**
+ * A new GATT service has been discovered.
+ * The service is added to the internal list and the search
+ * continues.
+ * @hide
+ */
+ public void onGetService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid) {
+ if (DBG) Log.d(TAG, "onGetService() - Device=" + address + " UUID=" + srvcUuid);
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ mServices.add(new BluetoothGattService(mDevice, srvcUuid.getUuid(),
+ srvcInstId, srvcType));
+ }
+
+ /**
+ * An included service has been found durig GATT discovery.
+ * The included service is added to the respective parent.
+ * @hide
+ */
+ public void onGetIncludedService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int inclSrvcType, int inclSrvcInstId,
+ ParcelUuid inclSrvcUuid) {
+ if (DBG) Log.d(TAG, "onGetIncludedService() - Device=" + address
+ + " UUID=" + srvcUuid + " Included=" + inclSrvcUuid);
+
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ BluetoothGattService service = getService(mDevice,
+ srvcUuid.getUuid(), srvcInstId, srvcType);
+ BluetoothGattService includedService = getService(mDevice,
+ inclSrvcUuid.getUuid(), inclSrvcInstId, inclSrvcType);
+
+ if (service != null && includedService != null) {
+ service.addIncludedService(includedService);
+ }
+ }
+
+ /**
+ * A new GATT characteristic has been discovered.
+ * Add the new characteristic to the relevant service and continue
+ * the remote device inspection.
+ * @hide
+ */
+ public void onGetCharacteristic(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int charProps) {
+ if (DBG) Log.d(TAG, "onGetCharacteristic() - Device=" + address + " UUID=" +
+ charUuid);
+
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service != null) {
+ service.addCharacteristic(new BluetoothGattCharacteristic(
+ service, charUuid.getUuid(), charInstId, charProps, 0));
+ }
+ }
+
+ /**
+ * A new GATT descriptor has been discovered.
+ * Finally, add the descriptor to the related characteristic.
+ * This should conclude the remote device update.
+ * @hide
+ */
+ public void onGetDescriptor(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ ParcelUuid descUuid) {
+ if (DBG) Log.d(TAG, "onGetDescriptor() - Device=" + address + " UUID=" + descUuid);
+
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid.getUuid());
+ if (characteristic == null) return;
+
+ characteristic.addDescriptor(new BluetoothGattDescriptor(
+ characteristic, descUuid.getUuid(), 0));
+ }
+
+ /**
+ * Remote search has been completed.
+ * The internal object structure should now reflect the state
+ * of the remote device database. Let the application know that
+ * we are done at this point.
+ * @hide
+ */
+ public void onSearchComplete(String address, int status) {
+ if (DBG) Log.d(TAG, "onSearchComplete() = Device=" + address + " Status=" + status);
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ try {
+ mCallback.onServicesDiscovered(BluetoothGatt.this, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Remote characteristic has been read.
+ * Updates the internal value.
+ * @hide
+ */
+ public void onCharacteristicRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid, byte[] value) {
+ if (DBG) Log.d(TAG, "onCharacteristicRead() - Device=" + address
+ + " UUID=" + charUuid + " Status=" + status);
+
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+ || status == GATT_INSUFFICIENT_ENCRYPTION)
+ && mAuthRetry == false) {
+ try {
+ mAuthRetry = true;
+ mService.readCharacteristic(mClientIf, address,
+ srvcType, srvcInstId, srvcUuid,
+ charInstId, charUuid, AUTHENTICATION_MITM);
+ return;
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ mAuthRetry = false;
+
+ BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid.getUuid(), charInstId);
+ if (characteristic == null) return;
+
+ if (status == 0) characteristic.setValue(value);
+
+ try {
+ mCallback.onCharacteristicRead(BluetoothGatt.this, characteristic, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Characteristic has been written to the remote device.
+ * Let the app know how we did...
+ * @hide
+ */
+ public void onCharacteristicWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid) {
+ if (DBG) Log.d(TAG, "onCharacteristicWrite() - Device=" + address
+ + " UUID=" + charUuid + " Status=" + status);
+
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid.getUuid(), charInstId);
+ if (characteristic == null) return;
+
+ if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+ || status == GATT_INSUFFICIENT_ENCRYPTION)
+ && mAuthRetry == false) {
+ try {
+ mAuthRetry = true;
+ mService.writeCharacteristic(mClientIf, address,
+ srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+ characteristic.getWriteType(), AUTHENTICATION_MITM,
+ characteristic.getValue());
+ return;
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ mAuthRetry = false;
+
+ try {
+ mCallback.onCharacteristicWrite(BluetoothGatt.this, characteristic, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Remote characteristic has been updated.
+ * Updates the internal value.
+ * @hide
+ */
+ public void onNotify(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ byte[] value) {
+ if (DBG) Log.d(TAG, "onNotify() - Device=" + address + " UUID=" + charUuid);
+
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid.getUuid(), charInstId);
+ if (characteristic == null) return;
+
+ characteristic.setValue(value);
+
+ try {
+ mCallback.onCharacteristicChanged(BluetoothGatt.this, characteristic);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Descriptor has been read.
+ * @hide
+ */
+ public void onDescriptorRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ ParcelUuid descrUuid, byte[] value) {
+ if (DBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " UUID=" + charUuid);
+
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid.getUuid(), charInstId);
+ if (characteristic == null) return;
+
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+ descrUuid.getUuid());
+ if (descriptor == null) return;
+
+ if (status == 0) descriptor.setValue(value);
+
+ if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+ || status == GATT_INSUFFICIENT_ENCRYPTION)
+ && mAuthRetry == false) {
+ try {
+ mAuthRetry = true;
+ mService.readDescriptor(mClientIf, address,
+ srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+ descrUuid, AUTHENTICATION_MITM);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ mAuthRetry = true;
+
+ try {
+ mCallback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Descriptor write operation complete.
+ * @hide
+ */
+ public void onDescriptorWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ ParcelUuid descrUuid) {
+ if (DBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " UUID=" + charUuid);
+
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ BluetoothGattService service = getService(mDevice, srvcUuid.getUuid(),
+ srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+ charUuid.getUuid(), charInstId);
+ if (characteristic == null) return;
+
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+ descrUuid.getUuid());
+ if (descriptor == null) return;
+
+ if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+ || status == GATT_INSUFFICIENT_ENCRYPTION)
+ && mAuthRetry == false) {
+ try {
+ mAuthRetry = true;
+ mService.writeDescriptor(mClientIf, address,
+ srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+ descrUuid, characteristic.getWriteType(),
+ AUTHENTICATION_MITM, descriptor.getValue());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ mAuthRetry = false;
+
+ try {
+ mCallback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Prepared write transaction completed (or aborted)
+ * @hide
+ */
+ public void onExecuteWrite(String address, int status) {
+ if (DBG) Log.d(TAG, "onExecuteWrite() - Device=" + address
+ + " status=" + status);
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ try {
+ mCallback.onReliableWriteCompleted(BluetoothGatt.this, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Remote device RSSI has been read
+ * @hide
+ */
+ public void onReadRemoteRssi(String address, int rssi, int status) {
+ if (DBG) Log.d(TAG, "onReadRemoteRssi() - Device=" + address +
+ " rssi=" + rssi + " status=" + status);
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+ try {
+ mCallback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+ };
+
+ /*package*/ BluetoothGatt(Context context, IBluetoothGatt iGatt, BluetoothDevice device) {
+ mContext = context;
+ mService = iGatt;
+ mDevice = device;
+ mServices = new ArrayList<BluetoothGattService>();
+
+ mConnState = CONN_STATE_IDLE;
+ }
+
+ /**
+ * Close this Bluetooth GATT client.
+ *
+ * Application should call this method as early as possible after it is done with
+ * this GATT client.
+ */
+ public void close() {
+ if (DBG) Log.d(TAG, "close()");
+
+ unregisterApp();
+ mConnState = CONN_STATE_CLOSED;
+ }
+
+ /**
+ * Returns a service by UUID, instance and type.
+ * @hide
+ */
+ /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
+ int instanceId, int type) {
+ for(BluetoothGattService svc : mServices) {
+ if (svc.getDevice().equals(device) &&
+ svc.getType() == type &&
+ svc.getInstanceId() == instanceId &&
+ svc.getUuid().equals(uuid)) {
+ return svc;
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Register an application callback to start using GATT.
+ *
+ * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
+ * is used to notify success or failure if the function returns true.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param callback GATT callback handler that will receive asynchronous callbacks.
+ * @return If true, the callback will be called to notify success or failure,
+ * false on immediate error
+ */
+ private boolean registerApp(BluetoothGattCallback callback) {
+ if (DBG) Log.d(TAG, "registerApp()");
+ if (mService == null) return false;
+
+ mCallback = callback;
+ UUID uuid = UUID.randomUUID();
+ if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
+
+ try {
+ mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Unregister the current application and callbacks.
+ */
+ private void unregisterApp() {
+ if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf);
+ if (mService == null || mClientIf == 0) return;
+
+ try {
+ mCallback = null;
+ mService.unregisterClient(mClientIf);
+ mClientIf = 0;
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Initiate a connection to a Bluetooth GATT capable device.
+ *
+ * <p>The connection may not be established right away, but will be
+ * completed when the remote device is available. A
+ * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
+ * invoked when the connection state changes as a result of this function.
+ *
+ * <p>The autoConnect paramter determines whether to actively connect to
+ * the remote device, or rather passively scan and finalize the connection
+ * when the remote device is in range/available. Generally, the first ever
+ * connection to a device should be direct (autoConnect set to false) and
+ * subsequent connections to known devices should be invoked with the
+ * autoConnect parameter set to true.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device to connect to
+ * @param autoConnect Whether to directly connect to the remote device (false)
+ * or to automatically connect as soon as the remote
+ * device becomes available (true).
+ * @return true, if the connection attempt was initiated successfully
+ */
+ /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback) {
+ if (DBG) Log.d(TAG, "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect);
+ synchronized(mStateLock) {
+ if (mConnState != CONN_STATE_IDLE) {
+ throw new IllegalStateException("Not idle");
+ }
+ mConnState = CONN_STATE_CONNECTING;
+ }
+ if (!registerApp(callback)) {
+ synchronized(mStateLock) {
+ mConnState = CONN_STATE_IDLE;
+ }
+ Log.e(TAG, "Failed to register callback");
+ return false;
+ }
+
+ // the connection will continue after successful callback registration
+ mAutoConnect = autoConnect;
+ return true;
+ }
+
+ /**
+ * Disconnects an established connection, or cancels a connection attempt
+ * currently in progress.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ */
+ public void disconnect() {
+ if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice.getAddress());
+ if (mService == null || mClientIf == 0) return;
+
+ try {
+ mService.clientDisconnect(mClientIf, mDevice.getAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Connect back to remote device.
+ *
+ * <p>This method is used to re-connect to a remote device after the
+ * connection has been dropped. If the device is not in range, the
+ * re-connection will be triggered once the device is back in range.
+ *
+ * @return true, if the connection attempt was initiated successfully
+ */
+ public boolean connect() {
+ try {
+ mService.clientConnect(mClientIf, mDevice.getAddress(),
+ false); // autoConnect is inverse of "isDirect"
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+ }
+
+ /**
+ * Return the remote bluetooth device this GATT client targets to
+ *
+ * @return remote bluetooth device
+ */
+ public BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ /**
+ * Discovers services offered by a remote device as well as their
+ * characteristics and descriptors.
+ *
+ * <p>This is an asynchronous operation. Once service discovery is completed,
+ * the {@link BluetoothGattCallback#onServicesDiscovered} callback is
+ * triggered. If the discovery was successful, the remote services can be
+ * retrieved using the {@link #getServices} function.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return true, if the remote service discovery has been started
+ */
+ public boolean discoverServices() {
+ if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice.getAddress());
+ if (mService == null || mClientIf == 0) return false;
+
+ mServices.clear();
+
+ try {
+ mService.discoverServices(mClientIf, mDevice.getAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns a list of GATT services offered by the remote device.
+ *
+ * <p>This function requires that service discovery has been completed
+ * for the given device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return List of services on the remote device. Returns an empty list
+ * if service discovery has not yet been performed.
+ */
+ public List<BluetoothGattService> getServices() {
+ List<BluetoothGattService> result =
+ new ArrayList<BluetoothGattService>();
+
+ for (BluetoothGattService service : mServices) {
+ if (service.getDevice().equals(mDevice)) {
+ result.add(service);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a {@link BluetoothGattService}, if the requested UUID is
+ * supported by the remote device.
+ *
+ * <p>This function requires that service discovery has been completed
+ * for the given device.
+ *
+ * <p>If multiple instances of the same service (as identified by UUID)
+ * exist, the first instance of the service is returned.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param uuid UUID of the requested service
+ * @return BluetoothGattService if supported, or null if the requested
+ * service is not offered by the remote device.
+ */
+ public BluetoothGattService getService(UUID uuid) {
+ for (BluetoothGattService service : mServices) {
+ if (service.getDevice().equals(mDevice) &&
+ service.getUuid().equals(uuid)) {
+ return service;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Reads the requested characteristic from the associated remote device.
+ *
+ * <p>This is an asynchronous operation. The result of the read operation
+ * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
+ * callback.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param characteristic Characteristic to read from the remote device
+ * @return true, if the read operation was initiated successfully
+ */
+ public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
+ if ((characteristic.getProperties() &
+ BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false;
+
+ if (DBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
+ if (mService == null || mClientIf == 0) return false;
+
+ BluetoothGattService service = characteristic.getService();
+ if (service == null) return false;
+
+ BluetoothDevice device = service.getDevice();
+ if (device == null) return false;
+
+ try {
+ mService.readCharacteristic(mClientIf, device.getAddress(),
+ service.getType(), service.getInstanceId(),
+ new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+ new ParcelUuid(characteristic.getUuid()), AUTHENTICATION_NONE);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Writes a given characteristic and its values to the associated remote device.
+ *
+ * <p>Once the write operation has been completed, the
+ * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
+ * reporting the result of the operation.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param characteristic Characteristic to write on the remote device
+ * @return true, if the write operation was initiated successfully
+ */
+ public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
+ if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
+ && (characteristic.getProperties() &
+ BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false;
+
+ if (DBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
+ if (mService == null || mClientIf == 0) return false;
+
+ BluetoothGattService service = characteristic.getService();
+ if (service == null) return false;
+
+ BluetoothDevice device = service.getDevice();
+ if (device == null) return false;
+
+ try {
+ mService.writeCharacteristic(mClientIf, device.getAddress(),
+ service.getType(), service.getInstanceId(),
+ new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+ new ParcelUuid(characteristic.getUuid()),
+ characteristic.getWriteType(), AUTHENTICATION_NONE,
+ characteristic.getValue());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Reads the value for a given descriptor from the associated remote device.
+ *
+ * <p>Once the read operation has been completed, the
+ * {@link BluetoothGattCallback#onDescriptorRead} callback is
+ * triggered, signaling the result of the operation.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param descriptor Descriptor value to read from the remote device
+ * @return true, if the read operation was initiated successfully
+ */
+ public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
+ if (DBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid());
+ if (mService == null || mClientIf == 0) return false;
+
+ BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
+ if (characteristic == null) return false;
+
+ BluetoothGattService service = characteristic.getService();
+ if (service == null) return false;
+
+ BluetoothDevice device = service.getDevice();
+ if (device == null) return false;
+
+ try {
+ mService.readDescriptor(mClientIf, device.getAddress(),
+ service.getType(), service.getInstanceId(),
+ new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+ new ParcelUuid(characteristic.getUuid()),
+ new ParcelUuid(descriptor.getUuid()), AUTHENTICATION_NONE);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Write the value of a given descriptor to the associated remote device.
+ *
+ * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is
+ * triggered to report the result of the write operation.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param descriptor Descriptor to write to the associated remote device
+ * @return true, if the write operation was initiated successfully
+ */
+ public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
+ if (DBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
+ if (mService == null || mClientIf == 0) return false;
+
+ BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
+ if (characteristic == null) return false;
+
+ BluetoothGattService service = characteristic.getService();
+ if (service == null) return false;
+
+ BluetoothDevice device = service.getDevice();
+ if (device == null) return false;
+
+ try {
+ mService.writeDescriptor(mClientIf, device.getAddress(),
+ service.getType(), service.getInstanceId(),
+ new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+ new ParcelUuid(characteristic.getUuid()),
+ new ParcelUuid(descriptor.getUuid()),
+ characteristic.getWriteType(), AUTHENTICATION_NONE,
+ descriptor.getValue());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Initiates a reliable write transaction for a given remote device.
+ *
+ * <p>Once a reliable write transaction has been initiated, all calls
+ * to {@link #writeCharacteristic} are sent to the remote device for
+ * verification and queued up for atomic execution. The application will
+ * receive an {@link BluetoothGattCallback#onCharacteristicWrite} callback
+ * in response to every {@link #writeCharacteristic} call and is responsible
+ * for verifying if the value has been transmitted accurately.
+ *
+ * <p>After all characteristics have been queued up and verified,
+ * {@link #executeReliableWrite} will execute all writes. If a characteristic
+ * was not written correctly, calling {@link #abortReliableWrite} will
+ * cancel the current transaction without commiting any values on the
+ * remote device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return true, if the reliable write transaction has been initiated
+ */
+ public boolean beginReliableWrite() {
+ if (DBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice.getAddress());
+ if (mService == null || mClientIf == 0) return false;
+
+ try {
+ mService.beginReliableWrite(mClientIf, mDevice.getAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Executes a reliable write transaction for a given remote device.
+ *
+ * <p>This function will commit all queued up characteristic write
+ * operations for a given remote device.
+ *
+ * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is
+ * invoked to indicate whether the transaction has been executed correctly.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return true, if the request to execute the transaction has been sent
+ */
+ public boolean executeReliableWrite() {
+ if (DBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice.getAddress());
+ if (mService == null || mClientIf == 0) return false;
+
+ try {
+ mService.endReliableWrite(mClientIf, mDevice.getAddress(), true);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Cancels a reliable write transaction for a given device.
+ *
+ * <p>Calling this function will discard all queued characteristic write
+ * operations for a given remote device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ */
+ public void abortReliableWrite(BluetoothDevice mDevice) {
+ if (DBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress());
+ if (mService == null || mClientIf == 0) return;
+
+ try {
+ mService.endReliableWrite(mClientIf, mDevice.getAddress(), false);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Enable or disable notifications/indications for a given characteristic.
+ *
+ * <p>Once notifications are enabled for a characteristic, a
+ * {@link BluetoothGattCallback#onCharacteristicChanged} callback will be
+ * triggered if the remote device indicates that the given characteristic
+ * has changed.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param characteristic The characteristic for which to enable notifications
+ * @param enable Set to true to enable notifications/indications
+ * @return true, if the requested notification status was set successfully
+ */
+ public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
+ boolean enable) {
+ if (DBG) Log.d(TAG, "setCharacteristicNotification() - uuid: " + characteristic.getUuid()
+ + " enable: " + enable);
+ if (mService == null || mClientIf == 0) return false;
+
+ BluetoothGattService service = characteristic.getService();
+ if (service == null) return false;
+
+ BluetoothDevice device = service.getDevice();
+ if (device == null) return false;
+
+ try {
+ mService.registerForNotification(mClientIf, device.getAddress(),
+ service.getType(), service.getInstanceId(),
+ new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+ new ParcelUuid(characteristic.getUuid()),
+ enable);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Clears the internal cache and forces a refresh of the services from the
+ * remote device.
+ * @hide
+ */
+ public boolean refresh() {
+ if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress());
+ if (mService == null || mClientIf == 0) return false;
+
+ try {
+ mService.refreshDevice(mClientIf, mDevice.getAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Read the RSSI for a connected remote device.
+ *
+ * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be
+ * invoked when the RSSI value has been read.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return true, if the RSSI value has been requested successfully
+ */
+ public boolean readRemoteRssi() {
+ if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice.getAddress());
+ if (mService == null || mClientIf == 0) return false;
+
+ try {
+ mService.readRemoteRssi(mClientIf, mDevice.getAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
+ * with {@link BluetoothProfile#GATT} as argument
+ *
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public int getConnectionState(BluetoothDevice device) {
+ throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
+ }
+
+ /**
+ * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
+ * with {@link BluetoothProfile#GATT} as argument
+ *
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public List<BluetoothDevice> getConnectedDevices() {
+ throw new UnsupportedOperationException
+ ("Use BluetoothManager#getConnectedDevices instead.");
+ }
+
+ /**
+ * Not supported - please use
+ * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
+ * with {@link BluetoothProfile#GATT} as first argument
+ *
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ throw new UnsupportedOperationException
+ ("Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattCallback.java b/core/java/android/bluetooth/BluetoothGattCallback.java
new file mode 100644
index 0000000..80ea4a6
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattCallback.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+/**
+ * This abstract class is used to implement {@link BluetoothGatt} callbacks.
+ */
+public abstract class BluetoothGattCallback {
+
+ /**
+ * Callback indicating when GATT client has connected/disconnected to/from a remote
+ * GATT server.
+ *
+ * @param gatt GATT client
+ * @param status Status of the connect or disconnect operation.
+ * {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
+ * @param newState Returns the new connection state. Can be one of
+ * {@link BluetoothProfile#STATE_DISCONNECTED} or
+ * {@link BluetoothProfile#STATE_CONNECTED}
+ */
+ public void onConnectionStateChange(BluetoothGatt gatt, int status,
+ int newState) {
+ }
+
+ /**
+ * Callback invoked when the list of remote services, characteristics and descriptors
+ * for the remote device have been updated, ie new services have been discovered.
+ *
+ * @param gatt GATT client invoked {@link BluetoothGatt#discoverServices}
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the remote device
+ * has been explored successfully.
+ */
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ }
+
+ /**
+ * Callback reporting the result of a characteristic read operation.
+ *
+ * @param gatt GATT client invoked {@link BluetoothGatt#readCharacteristic}
+ * @param characteristic Characteristic that was read from the associated
+ * remote device.
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation
+ * was completed successfully.
+ */
+ public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
+ int status) {
+ }
+
+ /**
+ * Callback indicating the result of a characteristic write operation.
+ *
+ * <p>If this callback is invoked while a reliable write transaction is
+ * in progress, the value of the characteristic represents the value
+ * reported by the remote device. An application should compare this
+ * value to the desired value to be written. If the values don't match,
+ * the application must abort the reliable write transaction.
+ *
+ * @param gatt GATT client invoked {@link BluetoothGatt#writeCharacteristic}
+ * @param characteristic Characteristic that was written to the associated
+ * remote device.
+ * @param status The result of the write operation
+ * {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
+ */
+ public void onCharacteristicWrite(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic, int status) {
+ }
+
+ /**
+ * Callback triggered as a result of a remote characteristic notification.
+ *
+ * @param gatt GATT client the characteristic is associated with
+ * @param characteristic Characteristic that has been updated as a result
+ * of a remote notification event.
+ */
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ }
+
+ /**
+ * Callback reporting the result of a descriptor read operation.
+ *
+ * @param gatt GATT client invoked {@link BluetoothGatt#readDescriptor}
+ * @param descriptor Descriptor that was read from the associated
+ * remote device.
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation
+ * was completed successfully
+ */
+ public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+ int status) {
+ }
+
+ /**
+ * Callback indicating the result of a descriptor write operation.
+ *
+ * @param gatt GATT client invoked {@link BluetoothGatt#writeDescriptor}
+ * @param descriptor Descriptor that was writte to the associated
+ * remote device.
+ * @param status The result of the write operation
+ * {@link BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
+ */
+ public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+ int status) {
+ }
+
+ /**
+ * Callback invoked when a reliable write transaction has been completed.
+ *
+ * @param gatt GATT client invoked {@link BluetoothGatt#executeReliableWrite}
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the reliable write
+ * transaction was executed successfully
+ */
+ public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
+ }
+
+ /**
+ * Callback reporting the RSSI for a remote device connection.
+ *
+ * This callback is triggered in response to the
+ * {@link BluetoothGatt#readRemoteRssi} function.
+ *
+ * @param gatt GATT client invoked {@link BluetoothGatt#readRemoteRssi}
+ * @param rssi The RSSI value for the remote device
+ * @param status {@link BluetoothGatt#GATT_SUCCESS} if the RSSI was read successfully
+ */
+ public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
new file mode 100644
index 0000000..033f079
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.bluetooth;
+
+import java.util.ArrayList;
+import java.util.IllegalFormatConversionException;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth GATT Characteristic
+ *
+ * <p>A GATT characteristic is a basic data element used to construct a GATT service,
+ * {@link BluetoothGattService}. The characteristic contains a value as well as
+ * additional information and optional GATT descriptors, {@link BluetoothGattDescriptor}.
+ */
+public class BluetoothGattCharacteristic {
+
+ /**
+ * Characteristic proprty: Characteristic is broadcastable.
+ */
+ public static final int PROPERTY_BROADCAST = 0x01;
+
+ /**
+ * Characteristic property: Characteristic is readable.
+ */
+ public static final int PROPERTY_READ = 0x02;
+
+ /**
+ * Characteristic property: Characteristic can be written without response.
+ */
+ public static final int PROPERTY_WRITE_NO_RESPONSE = 0x04;
+
+ /**
+ * Characteristic property: Characteristic can be written.
+ */
+ public static final int PROPERTY_WRITE = 0x08;
+
+ /**
+ * Characteristic property: Characteristic supports notification
+ */
+ public static final int PROPERTY_NOTIFY = 0x10;
+
+ /**
+ * Characteristic property: Characteristic supports indication
+ */
+ public static final int PROPERTY_INDICATE = 0x20;
+
+ /**
+ * Characteristic property: Characteristic supports write with signature
+ */
+ public static final int PROPERTY_SIGNED_WRITE = 0x40;
+
+ /**
+ * Characteristic property: Characteristic has extended properties
+ */
+ public static final int PROPERTY_EXTENDED_PROPS = 0x80;
+
+ /**
+ * Characteristic read permission
+ */
+ public static final int PERMISSION_READ = 0x01;
+
+ /**
+ * Characteristic permission: Allow encrypted read operations
+ */
+ public static final int PERMISSION_READ_ENCRYPTED = 0x02;
+
+ /**
+ * Characteristic permission: Allow reading with man-in-the-middle protection
+ */
+ public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04;
+
+ /**
+ * Characteristic write permission
+ */
+ public static final int PERMISSION_WRITE = 0x10;
+
+ /**
+ * Characteristic permission: Allow encrypted writes
+ */
+ public static final int PERMISSION_WRITE_ENCRYPTED = 0x20;
+
+ /**
+ * Characteristic permission: Allow encrypted writes with man-in-the-middle
+ * protection
+ */
+ public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40;
+
+ /**
+ * Characteristic permission: Allow signed write operations
+ */
+ public static final int PERMISSION_WRITE_SIGNED = 0x80;
+
+ /**
+ * Characteristic permission: Allow signed write operations with
+ * man-in-the-middle protection
+ */
+ public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100;
+
+ /**
+ * Write characteristic, requesting acknoledgement by the remote device
+ */
+ public static final int WRITE_TYPE_DEFAULT = 0x02;
+
+ /**
+ * Wrtite characteristic without requiring a response by the remote device
+ */
+ public static final int WRITE_TYPE_NO_RESPONSE = 0x01;
+
+ /**
+ * Write characteristic including authentication signature
+ */
+ public static final int WRITE_TYPE_SIGNED = 0x04;
+
+ /**
+ * Characteristic value format type uint8
+ */
+ public static final int FORMAT_UINT8 = 0x11;
+
+ /**
+ * Characteristic value format type uint16
+ */
+ public static final int FORMAT_UINT16 = 0x12;
+
+ /**
+ * Characteristic value format type uint32
+ */
+ public static final int FORMAT_UINT32 = 0x14;
+
+ /**
+ * Characteristic value format type sint8
+ */
+ public static final int FORMAT_SINT8 = 0x21;
+
+ /**
+ * Characteristic value format type sint16
+ */
+ public static final int FORMAT_SINT16 = 0x22;
+
+ /**
+ * Characteristic value format type sint32
+ */
+ public static final int FORMAT_SINT32 = 0x24;
+
+ /**
+ * Characteristic value format type sfloat (16-bit float)
+ */
+ public static final int FORMAT_SFLOAT = 0x32;
+
+ /**
+ * Characteristic value format type float (32-bit float)
+ */
+ public static final int FORMAT_FLOAT = 0x34;
+
+
+ /**
+ * The UUID of this characteristic.
+ * @hide
+ */
+ protected UUID mUuid;
+
+ /**
+ * Instance ID for this characteristic.
+ * @hide
+ */
+ protected int mInstance;
+
+ /**
+ * Characteristic properties.
+ * @hide
+ */
+ protected int mProperties;
+
+ /**
+ * Characteristic permissions.
+ * @hide
+ */
+ protected int mPermissions;
+
+ /**
+ * Key size (default = 16).
+ * @hide
+ */
+ protected int mKeySize = 16;
+
+ /**
+ * Write type for this characteristic.
+ * See WRITE_TYPE_* constants.
+ * @hide
+ */
+ protected int mWriteType;
+
+ /**
+ * Back-reference to the service this characteristic belongs to.
+ * @hide
+ */
+ protected BluetoothGattService mService;
+
+ /**
+ * The cached value of this characteristic.
+ * @hide
+ */
+ protected byte[] mValue;
+
+ /**
+ * List of descriptors included in this characteristic.
+ */
+ protected List<BluetoothGattDescriptor> mDescriptors;
+
+ /**
+ * Create a new BluetoothGattCharacteristic.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param uuid The UUID for this characteristic
+ * @param properties Properties of this characteristic
+ * @param permissions Permissions for this characteristic
+ */
+ public BluetoothGattCharacteristic(UUID uuid, int properties, int permissions) {
+ initCharacteristic(null, uuid, 0, properties, permissions);
+ }
+
+ /**
+ * Create a new BluetoothGattCharacteristic
+ * @hide
+ */
+ /*package*/ BluetoothGattCharacteristic(BluetoothGattService service,
+ UUID uuid, int instanceId,
+ int properties, int permissions) {
+ initCharacteristic(service, uuid, instanceId, properties, permissions);
+ }
+
+ private void initCharacteristic(BluetoothGattService service,
+ UUID uuid, int instanceId,
+ int properties, int permissions) {
+ mUuid = uuid;
+ mInstance = instanceId;
+ mProperties = properties;
+ mPermissions = permissions;
+ mService = service;
+ mValue = null;
+ mDescriptors = new ArrayList<BluetoothGattDescriptor>();
+
+ if ((mProperties & PROPERTY_WRITE_NO_RESPONSE) != 0) {
+ mWriteType = WRITE_TYPE_NO_RESPONSE;
+ } else {
+ mWriteType = WRITE_TYPE_DEFAULT;
+ }
+ }
+
+ /**
+ * Returns the deisred key size.
+ * @hide
+ */
+ /*package*/ int getKeySize() {
+ return mKeySize;
+ }
+
+ /**
+ * Adds a descriptor to this characteristic.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param descriptor Descriptor to be added to this characteristic.
+ * @return true, if the descriptor was added to the characteristic
+ */
+ public boolean addDescriptor(BluetoothGattDescriptor descriptor) {
+ mDescriptors.add(descriptor);
+ descriptor.setCharacteristic(this);
+ return true;
+ }
+
+ /**
+ * Returns the service this characteristic belongs to.
+ * @return The asscociated service
+ */
+ public BluetoothGattService getService() {
+ return mService;
+ }
+
+ /**
+ * Sets the service associated with this device.
+ * @hide
+ */
+ /*package*/ void setService(BluetoothGattService service) {
+ mService = service;
+ }
+
+ /**
+ * Returns the UUID of this characteristic
+ *
+ * @return UUID of this characteristic
+ */
+ public UUID getUuid() {
+ return mUuid;
+ }
+
+ /**
+ * Returns the instance ID for this characteristic.
+ *
+ * <p>If a remote device offers multiple characteristics with the same UUID,
+ * the instance ID is used to distuinguish between characteristics.
+ *
+ * @return Instance ID of this characteristic
+ */
+ public int getInstanceId() {
+ return mInstance;
+ }
+
+ /**
+ * Returns the properties of this characteristic.
+ *
+ * <p>The properties contain a bit mask of property flags indicating
+ * the features of this characteristic.
+ *
+ * @return Properties of this characteristic
+ */
+ public int getProperties() {
+ return mProperties;
+ }
+
+ /**
+ * Returns the permissions for this characteristic.
+ *
+ * @return Permissions of this characteristic
+ */
+ public int getPermissions() {
+ return mPermissions;
+ }
+
+ /**
+ * Gets the write type for this characteristic.
+ *
+ * @return Write type for this characteristic
+ */
+ public int getWriteType() {
+ return mWriteType;
+ }
+
+ /**
+ * Set the write type for this characteristic
+ *
+ * <p>Setting the write type of a characteristic determines how the
+ * {@link BluetoothGatt#writeCharacteristic} function write this
+ * characteristic.
+ *
+ * @param writeType The write type to for this characteristic. Can be one
+ * of:
+ * {@link #WRITE_TYPE_DEFAULT},
+ * {@link #WRITE_TYPE_NO_RESPONSE} or
+ * {@link #WRITE_TYPE_SIGNED}.
+ */
+ public void setWriteType(int writeType) {
+ mWriteType = writeType;
+ }
+
+ /**
+ * Set the desired key size.
+ * @hide
+ */
+ public void setKeySize(int keySize) {
+ mKeySize = keySize;
+ }
+
+ /**
+ * Returns a list of descriptors for this characteristic.
+ *
+ * @return Descriptors for this characteristic
+ */
+ public List<BluetoothGattDescriptor> getDescriptors() {
+ return mDescriptors;
+ }
+
+ /**
+ * Returns a descriptor with a given UUID out of the list of
+ * descriptors for this characteristic.
+ *
+ * @return GATT descriptor object or null if no descriptor with the
+ * given UUID was found.
+ */
+ public BluetoothGattDescriptor getDescriptor(UUID uuid) {
+ for(BluetoothGattDescriptor descriptor : mDescriptors) {
+ if (descriptor.getUuid().equals(uuid)) {
+ return descriptor;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the stored value for this characteristic.
+ *
+ * <p>This function returns the stored value for this characteristic as
+ * retrieved by calling {@link BluetoothGatt#readCharacteristic}. The cached
+ * value of the characteristic is updated as a result of a read characteristic
+ * operation or if a characteristic update notification has been received.
+ *
+ * @return Cached value of the characteristic
+ */
+ public byte[] getValue() {
+ return mValue;
+ }
+
+ /**
+ * Return the stored value of this characteristic.
+ *
+ * <p>The formatType parameter determines how the characteristic value
+ * is to be interpreted. For example, settting formatType to
+ * {@link #FORMAT_UINT16} specifies that the first two bytes of the
+ * characteristic value at the given offset are interpreted to generate the
+ * return value.
+ *
+ * @param formatType The format type used to interpret the characteristic
+ * value.
+ * @param offset Offset at which the integer value can be found.
+ * @return Cached value of the characteristic or null of offset exceeds
+ * value size.
+ */
+ public Integer getIntValue(int formatType, int offset) {
+ if ((offset + getTypeLen(formatType)) > mValue.length) return null;
+
+ switch (formatType) {
+ case FORMAT_UINT8:
+ return unsignedByteToInt(mValue[offset]);
+
+ case FORMAT_UINT16:
+ return unsignedBytesToInt(mValue[offset], mValue[offset+1]);
+
+ case FORMAT_UINT32:
+ return unsignedBytesToInt(mValue[offset], mValue[offset+1],
+ mValue[offset+2], mValue[offset+3]);
+ case FORMAT_SINT8:
+ return unsignedToSigned(unsignedByteToInt(mValue[offset]), 8);
+
+ case FORMAT_SINT16:
+ return unsignedToSigned(unsignedBytesToInt(mValue[offset],
+ mValue[offset+1]), 16);
+
+ case FORMAT_SINT32:
+ return unsignedToSigned(unsignedBytesToInt(mValue[offset],
+ mValue[offset+1], mValue[offset+2], mValue[offset+3]), 32);
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the stored value of this characteristic.
+ * <p>See {@link #getValue} for details.
+ *
+ * @param formatType The format type used to interpret the characteristic
+ * value.
+ * @param offset Offset at which the float value can be found.
+ * @return Cached value of the characteristic at a given offset or null
+ * if the requested offset exceeds the value size.
+ */
+ public Float getFloatValue(int formatType, int offset) {
+ if ((offset + getTypeLen(formatType)) > mValue.length) return null;
+
+ switch (formatType) {
+ case FORMAT_SFLOAT:
+ return bytesToFloat(mValue[offset], mValue[offset+1]);
+
+ case FORMAT_FLOAT:
+ return bytesToFloat(mValue[offset], mValue[offset+1],
+ mValue[offset+2], mValue[offset+3]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the stored value of this characteristic.
+ * <p>See {@link #getValue} for details.
+ *
+ * @param offset Offset at which the string value can be found.
+ * @return Cached value of the characteristic
+ */
+ public String getStringValue(int offset) {
+ if (offset > mValue.length) return null;
+ byte[] strBytes = new byte[mValue.length - offset];
+ for (int i=0; i != (mValue.length-offset); ++i) strBytes[i] = mValue[offset+i];
+ return new String(strBytes);
+ }
+
+ /**
+ * Updates the locally stored value of this characteristic.
+ *
+ * <p>This function modifies the locally stored cached value of this
+ * characteristic. To send the value to the remote device, call
+ * {@link BluetoothGatt#writeCharacteristic} to send the value to the
+ * remote device.
+ *
+ * @param value New value for this characteristic
+ * @return true if the locally stored value has been set, false if the
+ * requested value could not be stored locally.
+ */
+ public boolean setValue(byte[] value) {
+ mValue = value;
+ return true;
+ }
+
+ /**
+ * Set the locally stored value of this characteristic.
+ * <p>See {@link #setValue(byte[])} for details.
+ *
+ * @param value New value for this characteristic
+ * @param formatType Integer format type used to transform the value parameter
+ * @param offset Offset at which the value should be placed
+ * @return true if the locally stored value has been set
+ */
+ public boolean setValue(int value, int formatType, int offset) {
+ int len = offset + getTypeLen(formatType);
+ if (mValue == null) mValue = new byte[len];
+ if (len > mValue.length) return false;
+
+ switch (formatType) {
+ case FORMAT_SINT8:
+ value = intToSignedBits(value, 8);
+ // Fall-through intended
+ case FORMAT_UINT8:
+ mValue[offset] = (byte)(value & 0xFF);
+ break;
+
+ case FORMAT_SINT16:
+ value = intToSignedBits(value, 16);
+ // Fall-through intended
+ case FORMAT_UINT16:
+ mValue[offset++] = (byte)(value & 0xFF);
+ mValue[offset] = (byte)((value >> 8) & 0xFF);
+ break;
+
+ case FORMAT_SINT32:
+ value = intToSignedBits(value, 32);
+ // Fall-through intended
+ case FORMAT_UINT32:
+ mValue[offset++] = (byte)(value & 0xFF);
+ mValue[offset++] = (byte)((value >> 8) & 0xFF);
+ mValue[offset++] = (byte)((value >> 16) & 0xFF);
+ mValue[offset] = (byte)((value >> 24) & 0xFF);
+ break;
+
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set the locally stored value of this characteristic.
+ * <p>See {@link #setValue(byte[])} for details.
+ *
+ * @param mantissa Mantissa for this characteristic
+ * @param exponent exponent value for this characteristic
+ * @param formatType Float format type used to transform the value parameter
+ * @param offset Offset at which the value should be placed
+ * @return true if the locally stored value has been set
+ */
+ public boolean setValue(int mantissa, int exponent, int formatType, int offset) {
+ int len = offset + getTypeLen(formatType);
+ if (mValue == null) mValue = new byte[len];
+ if (len > mValue.length) return false;
+
+ switch (formatType) {
+ case FORMAT_SFLOAT:
+ mantissa = intToSignedBits(mantissa, 12);
+ exponent = intToSignedBits(exponent, 4);
+ mValue[offset++] = (byte)(mantissa & 0xFF);
+ mValue[offset] = (byte)((mantissa >> 8) & 0x0F);
+ mValue[offset] += (byte)((exponent & 0x0F) << 4);
+ break;
+
+ case FORMAT_FLOAT:
+ mantissa = intToSignedBits(mantissa, 24);
+ exponent = intToSignedBits(exponent, 8);
+ mValue[offset++] = (byte)(mantissa & 0xFF);
+ mValue[offset++] = (byte)((mantissa >> 8) & 0xFF);
+ mValue[offset++] = (byte)((mantissa >> 16) & 0xFF);
+ mValue[offset] += (byte)(exponent & 0xFF);
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Set the locally stored value of this characteristic.
+ * <p>See {@link #setValue(byte[])} for details.
+ *
+ * @param value New value for this characteristic
+ * @return true if the locally stored value has been set
+ */
+ public boolean setValue(String value) {
+ mValue = value.getBytes();
+ return true;
+ }
+
+ /**
+ * Returns the size of a give value type.
+ */
+ private int getTypeLen(int formatType) {
+ return formatType & 0xF;
+ }
+
+ /**
+ * Convert a signed byte to an unsigned int.
+ */
+ private int unsignedByteToInt(byte b) {
+ return b & 0xFF;
+ }
+
+ /**
+ * Convert signed bytes to a 16-bit unsigned int.
+ */
+ private int unsignedBytesToInt(byte b0, byte b1) {
+ return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8));
+ }
+
+ /**
+ * Convert signed bytes to a 32-bit unsigned int.
+ */
+ private int unsignedBytesToInt(byte b0, byte b1, byte b2, byte b3) {
+ return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8))
+ + (unsignedByteToInt(b2) << 16) + (unsignedByteToInt(b3) << 24);
+ }
+
+ /**
+ * Convert signed bytes to a 16-bit short float value.
+ */
+ private float bytesToFloat(byte b0, byte b1) {
+ int mantissa = unsignedToSigned(unsignedByteToInt(b0)
+ + ((unsignedByteToInt(b1) & 0x0F) << 8), 12);
+ int exponent = unsignedToSigned(unsignedByteToInt(b1) >> 4, 4);
+ return (float)(mantissa * Math.pow(10, exponent));
+ }
+
+ /**
+ * Convert signed bytes to a 32-bit short float value.
+ */
+ private float bytesToFloat(byte b0, byte b1, byte b2, byte b3) {
+ int mantissa = unsignedToSigned(unsignedByteToInt(b0)
+ + (unsignedByteToInt(b1) << 8)
+ + (unsignedByteToInt(b2) << 16), 24);
+ return (float)(mantissa * Math.pow(10, b3));
+ }
+
+ /**
+ * Convert an unsigned integer value to a two's-complement encoded
+ * signed value.
+ */
+ private int unsignedToSigned(int unsigned, int size) {
+ if ((unsigned & (1 << size-1)) != 0) {
+ unsigned = -1 * ((1 << size-1) - (unsigned & ((1 << size-1) - 1)));
+ }
+ return unsigned;
+ }
+
+ /**
+ * Convert an integer into the signed bits of a given length.
+ */
+ private int intToSignedBits(int i, int size) {
+ if (i < 0) {
+ i = (1 << size-1) + (i & ((1 << size-1) - 1));
+ }
+ return i;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java
new file mode 100644
index 0000000..1cd6878
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth GATT Descriptor
+ *
+ * <p> GATT Descriptors contain additional information and attributes of a GATT
+ * characteristic, {@link BluetoothGattCharacteristic}. They can be used to describe
+ * the characteristic's features or to control certain behaviours of the characteristic.
+ */
+public class BluetoothGattDescriptor {
+
+ /**
+ * Value used to enable notification for a client configuration descriptor
+ */
+ public static final byte[] ENABLE_NOTIFICATION_VALUE = {0x01, 0x00};
+
+ /**
+ * Value used to enable indication for a client configuration descriptor
+ */
+ public static final byte[] ENABLE_INDICATION_VALUE = {0x02, 0x00};
+
+ /**
+ * Value used to disable notifications or indicatinos
+ */
+ public static final byte[] DISABLE_NOTIFICATION_VALUE = {0x00, 0x00};
+
+ /**
+ * Descriptor read permission
+ */
+ public static final int PERMISSION_READ = 0x01;
+
+ /**
+ * Descriptor permission: Allow encrypted read operations
+ */
+ public static final int PERMISSION_READ_ENCRYPTED = 0x02;
+
+ /**
+ * Descriptor permission: Allow reading with man-in-the-middle protection
+ */
+ public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04;
+
+ /**
+ * Descriptor write permission
+ */
+ public static final int PERMISSION_WRITE = 0x10;
+
+ /**
+ * Descriptor permission: Allow encrypted writes
+ */
+ public static final int PERMISSION_WRITE_ENCRYPTED = 0x20;
+
+ /**
+ * Descriptor permission: Allow encrypted writes with man-in-the-middle
+ * protection
+ */
+ public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40;
+
+ /**
+ * Descriptor permission: Allow signed write operations
+ */
+ public static final int PERMISSION_WRITE_SIGNED = 0x80;
+
+ /**
+ * Descriptor permission: Allow signed write operations with
+ * man-in-the-middle protection
+ */
+ public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100;
+
+ /**
+ * The UUID of this descriptor.
+ * @hide
+ */
+ protected UUID mUuid;
+
+ /**
+ * Permissions for this descriptor
+ * @hide
+ */
+ protected int mPermissions;
+
+ /**
+ * Back-reference to the characteristic this descriptor belongs to.
+ * @hide
+ */
+ protected BluetoothGattCharacteristic mCharacteristic;
+
+ /**
+ * The value for this descriptor.
+ * @hide
+ */
+ protected byte[] mValue;
+
+ /**
+ * Create a new BluetoothGattDescriptor.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param uuid The UUID for this descriptor
+ * @param permissions Permissions for this descriptor
+ */
+ public BluetoothGattDescriptor(UUID uuid, int permissions) {
+ initDescriptor(null, uuid, permissions);
+ }
+
+ /**
+ * Create a new BluetoothGattDescriptor.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param characteristic The characteristic this descriptor belongs to
+ * @param uuid The UUID for this descriptor
+ * @param permissions Permissions for this descriptor
+ */
+ /*package*/ BluetoothGattDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid,
+ int permissions) {
+ initDescriptor(characteristic, uuid, permissions);
+ }
+
+ private void initDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid,
+ int permissions) {
+ mCharacteristic = characteristic;
+ mUuid = uuid;
+ mPermissions = permissions;
+ }
+
+ /**
+ * Returns the characteristic this descriptor belongs to.
+ * @return The characteristic.
+ */
+ public BluetoothGattCharacteristic getCharacteristic() {
+ return mCharacteristic;
+ }
+
+ /**
+ * Set the back-reference to the associated characteristic
+ * @hide
+ */
+ /*package*/ void setCharacteristic(BluetoothGattCharacteristic characteristic) {
+ mCharacteristic = characteristic;
+ }
+
+ /**
+ * Returns the UUID of this descriptor.
+ *
+ * @return UUID of this descriptor
+ */
+ public UUID getUuid() {
+ return mUuid;
+ }
+
+ /**
+ * Returns the permissions for this descriptor.
+ *
+ * @return Permissions of this descriptor
+ */
+ public int getPermissions() {
+ return mPermissions;
+ }
+
+ /**
+ * Returns the stored value for this descriptor
+ *
+ * <p>This function returns the stored value for this descriptor as
+ * retrieved by calling {@link BluetoothGatt#readDescriptor}. The cached
+ * value of the descriptor is updated as a result of a descriptor read
+ * operation.
+ *
+ * @return Cached value of the descriptor
+ */
+ public byte[] getValue() {
+ return mValue;
+ }
+
+ /**
+ * Updates the locally stored value of this descriptor.
+ *
+ * <p>This function modifies the locally stored cached value of this
+ * descriptor. To send the value to the remote device, call
+ * {@link BluetoothGatt#writeDescriptor} to send the value to the
+ * remote device.
+ *
+ * @param value New value for this descriptor
+ * @return true if the locally stored value has been set, false if the
+ * requested value could not be stored locally.
+ */
+ public boolean setValue(byte[] value) {
+ mValue = value;
+ return true;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
new file mode 100644
index 0000000..78d536b
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -0,0 +1,694 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothStateChangeCallback;
+
+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.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Public API for the Bluetooth GATT Profile server role.
+ *
+ * <p>This class provides Bluetooth GATT server role functionality,
+ * allowing applications to create and advertise Bluetooth Smart services
+ * and characteristics.
+ *
+ * <p>BluetoothGattServer is a proxy object for controlling the Bluetooth Service
+ * via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the
+ * BluetoothGatt proxy object.
+ */
+public final class BluetoothGattServer implements BluetoothProfile {
+ private static final String TAG = "BluetoothGattServer";
+ private static final boolean DBG = true;
+
+ private final Context mContext;
+ private BluetoothAdapter mAdapter;
+ private IBluetoothGatt mService;
+ private BluetoothGattServerCallback mCallback;
+
+ private Object mServerIfLock = new Object();
+ private int mServerIf;
+ private List<BluetoothGattService> mServices;
+
+ private static final int CALLBACK_REG_TIMEOUT = 10000;
+
+ /**
+ * Bluetooth GATT interface callbacks
+ */
+ private final IBluetoothGattServerCallback mBluetoothGattServerCallback =
+ new IBluetoothGattServerCallback.Stub() {
+ /**
+ * Application interface registered - app is ready to go
+ * @hide
+ */
+ public void onServerRegistered(int status, int serverIf) {
+ if (DBG) Log.d(TAG, "onServerRegistered() - status=" + status
+ + " serverIf=" + serverIf);
+ synchronized(mServerIfLock) {
+ if (mCallback != null) {
+ mServerIf = serverIf;
+ mServerIfLock.notify();
+ } else {
+ // registration timeout
+ Log.e(TAG, "onServerRegistered: mCallback is null");
+ }
+ }
+ }
+
+ /**
+ * Callback reporting an LE scan result.
+ * @hide
+ */
+ public void onScanResult(String address, int rssi, byte[] advData) {
+ if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
+ // no op
+ }
+
+ /**
+ * Server connection state changed
+ * @hide
+ */
+ public void onServerConnectionState(int status, int serverIf,
+ boolean connected, String address) {
+ if (DBG) Log.d(TAG, "onServerConnectionState() - status=" + status
+ + " serverIf=" + serverIf + " device=" + address);
+ try {
+ mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status,
+ connected ? BluetoothProfile.STATE_CONNECTED :
+ BluetoothProfile.STATE_DISCONNECTED);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Service has been added
+ * @hide
+ */
+ public void onServiceAdded(int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcId) {
+ UUID srvcUuid = srvcId.getUuid();
+ if (DBG) Log.d(TAG, "onServiceAdded() - service=" + srvcUuid
+ + "status=" + status);
+
+ BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+ if (service == null) return;
+
+ try {
+ mCallback.onServiceAdded((int)status, service);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Remote client characteristic read request.
+ * @hide
+ */
+ public void onCharacteristicReadRequest(String address, int transId,
+ int offset, boolean isLong, int srvcType, int srvcInstId,
+ ParcelUuid srvcId, int charInstId, ParcelUuid charId) {
+ UUID srvcUuid = srvcId.getUuid();
+ UUID charUuid = charId.getUuid();
+ if (DBG) Log.d(TAG, "onCharacteristicReadRequest() - "
+ + "service=" + srvcUuid + ", characteristic=" + charUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
+ if (characteristic == null) return;
+
+ try {
+ mCallback.onCharacteristicReadRequest(device, transId, offset, characteristic);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Remote client descriptor read request.
+ * @hide
+ */
+ public void onDescriptorReadRequest(String address, int transId,
+ int offset, boolean isLong, int srvcType, int srvcInstId,
+ ParcelUuid srvcId, int charInstId, ParcelUuid charId,
+ ParcelUuid descrId) {
+ UUID srvcUuid = srvcId.getUuid();
+ UUID charUuid = charId.getUuid();
+ UUID descrUuid = descrId.getUuid();
+ if (DBG) Log.d(TAG, "onCharacteristicReadRequest() - "
+ + "service=" + srvcUuid + ", characteristic=" + charUuid
+ + "descriptor=" + descrUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
+ if (characteristic == null) return;
+
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descrUuid);
+ if (descriptor == null) return;
+
+ try {
+ mCallback.onDescriptorReadRequest(device, transId, offset, descriptor);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Remote client characteristic write request.
+ * @hide
+ */
+ public void onCharacteristicWriteRequest(String address, int transId,
+ int offset, int length, boolean isPrep, boolean needRsp,
+ int srvcType, int srvcInstId, ParcelUuid srvcId,
+ int charInstId, ParcelUuid charId, byte[] value) {
+ UUID srvcUuid = srvcId.getUuid();
+ UUID charUuid = charId.getUuid();
+ if (DBG) Log.d(TAG, "onCharacteristicWriteRequest() - "
+ + "service=" + srvcUuid + ", characteristic=" + charUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
+ if (characteristic == null) return;
+
+ try {
+ mCallback.onCharacteristicWriteRequest(device, transId, characteristic,
+ isPrep, needRsp, offset, value);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+
+ }
+
+ /**
+ * Remote client descriptor write request.
+ * @hide
+ */
+ public void onDescriptorWriteRequest(String address, int transId,
+ int offset, int length, boolean isPrep, boolean needRsp,
+ int srvcType, int srvcInstId, ParcelUuid srvcId,
+ int charInstId, ParcelUuid charId, ParcelUuid descrId,
+ byte[] value) {
+ UUID srvcUuid = srvcId.getUuid();
+ UUID charUuid = charId.getUuid();
+ UUID descrUuid = descrId.getUuid();
+ if (DBG) Log.d(TAG, "onDescriptorWriteRequest() - "
+ + "service=" + srvcUuid + ", characteristic=" + charUuid
+ + "descriptor=" + descrUuid);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+
+ BluetoothGattService service = getService(srvcUuid, srvcInstId, srvcType);
+ if (service == null) return;
+
+ BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid);
+ if (characteristic == null) return;
+
+ BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descrUuid);
+ if (descriptor == null) return;
+
+ try {
+ mCallback.onDescriptorWriteRequest(device, transId, descriptor,
+ isPrep, needRsp, offset, value);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+
+ /**
+ * Execute pending writes.
+ * @hide
+ */
+ public void onExecuteWrite(String address, int transId,
+ boolean execWrite) {
+ if (DBG) Log.d(TAG, "onExecuteWrite() - "
+ + "device=" + address + ", transId=" + transId
+ + "execWrite=" + execWrite);
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(address);
+ if (device == null) return;
+
+ try {
+ mCallback.onExecuteWrite(device, transId, execWrite);
+ } catch (Exception ex) {
+ Log.w(TAG, "Unhandled exception: " + ex);
+ }
+ }
+ };
+
+ /**
+ * Create a BluetoothGattServer proxy object.
+ */
+ /*package*/ BluetoothGattServer(Context context, IBluetoothGatt iGatt) {
+ mContext = context;
+ mService = iGatt;
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mCallback = null;
+ mServerIf = 0;
+ mServices = new ArrayList<BluetoothGattService>();
+ }
+
+ /**
+ * Close this GATT server instance.
+ *
+ * Application should call this method as early as possible after it is done with
+ * this GATT server.
+ */
+ public void close() {
+ if (DBG) Log.d(TAG, "close()");
+ unregisterCallback();
+ }
+
+ /**
+ * Register an application callback to start using GattServer.
+ *
+ * <p>This is an asynchronous call. The callback is used to notify
+ * success or failure if the function returns true.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param callback GATT callback handler that will receive asynchronous
+ * callbacks.
+ * @return true, the callback will be called to notify success or failure,
+ * false on immediate error
+ */
+ /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
+ if (DBG) Log.d(TAG, "registerCallback()");
+ if (mService == null) {
+ Log.e(TAG, "GATT service not available");
+ return false;
+ }
+ UUID uuid = UUID.randomUUID();
+ if (DBG) Log.d(TAG, "registerCallback() - UUID=" + uuid);
+
+ synchronized(mServerIfLock) {
+ if (mCallback != null) {
+ Log.e(TAG, "App can register callback only once");
+ return false;
+ }
+
+ mCallback = callback;
+ try {
+ mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ mCallback = null;
+ return false;
+ }
+
+ try {
+ mServerIfLock.wait(CALLBACK_REG_TIMEOUT);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "" + e);
+ mCallback = null;
+ }
+
+ if (mServerIf == 0) {
+ mCallback = null;
+ return false;
+ } else {
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Unregister the current application and callbacks.
+ */
+ private void unregisterCallback() {
+ if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf);
+ if (mService == null || mServerIf == 0) return;
+
+ try {
+ mCallback = null;
+ mService.unregisterServer(mServerIf);
+ mServerIf = 0;
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Returns a service by UUID, instance and type.
+ * @hide
+ */
+ /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) {
+ for(BluetoothGattService svc : mServices) {
+ if (svc.getType() == type &&
+ svc.getInstanceId() == instanceId &&
+ svc.getUuid().equals(uuid)) {
+ return svc;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Initiate a connection to a Bluetooth GATT capable device.
+ *
+ * <p>The connection may not be established right away, but will be
+ * completed when the remote device is available. A
+ * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be
+ * invoked when the connection state changes as a result of this function.
+ *
+ * <p>The autoConnect paramter determines whether to actively connect to
+ * the remote device, or rather passively scan and finalize the connection
+ * when the remote device is in range/available. Generally, the first ever
+ * connection to a device should be direct (autoConnect set to false) and
+ * subsequent connections to known devices should be invoked with the
+ * autoConnect parameter set to true.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param autoConnect Whether to directly connect to the remote device (false)
+ * or to automatically connect as soon as the remote
+ * device becomes available (true).
+ * @return true, if the connection attempt was initiated successfully
+ */
+ public boolean connect(BluetoothDevice device, boolean autoConnect) {
+ if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect);
+ if (mService == null || mServerIf == 0) return false;
+
+ try {
+ mService.serverConnect(mServerIf, device.getAddress(),
+ autoConnect ? false : true); // autoConnect is inverse of "isDirect"
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Disconnects an established connection, or cancels a connection attempt
+ * currently in progress.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote device
+ */
+ public void cancelConnection(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress());
+ if (mService == null || mServerIf == 0) return;
+
+ try {
+ mService.serverDisconnect(mServerIf, device.getAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Send a response to a read or write request to a remote device.
+ *
+ * <p>This function must be invoked in when a remote read/write request
+ * is received by one of these callback methods:
+ *
+ * <ul>
+ * <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest}
+ * <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest}
+ * <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest}
+ * <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest}
+ * </ul>
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device The remote device to send this response to
+ * @param requestId The ID of the request that was received with the callback
+ * @param status The status of the request to be sent to the remote devices
+ * @param offset Value offset for partial read/write response
+ * @param value The value of the attribute that was read/written (optional)
+ */
+ public boolean sendResponse(BluetoothDevice device, int requestId,
+ int status, int offset, byte[] value) {
+ if (DBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress());
+ if (mService == null || mServerIf == 0) return false;
+
+ try {
+ mService.sendResponse(mServerIf, device.getAddress(), requestId,
+ status, offset, value);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Send a notification or indication that a local characteristic has been
+ * updated.
+ *
+ * <p>A notification or indication is sent to the remote device to signal
+ * that the characteristic has been updated. This function should be invoked
+ * for every client that requests notifications/indications by writing
+ * to the "Client Configuration" descriptor for the given characteristic.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device The remote device to receive the notification/indication
+ * @param characteristic The local characteristic that has been updated
+ * @param confirm true to request confirmation from the client (indication),
+ * false to send a notification
+ * @return true, if the notification has been triggered successfully
+ */
+ public boolean notifyCharacteristicChanged(BluetoothDevice device,
+ BluetoothGattCharacteristic characteristic, boolean confirm) {
+ if (DBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
+ if (mService == null || mServerIf == 0) return false;
+
+ BluetoothGattService service = characteristic.getService();
+ if (service == null) return false;
+
+ try {
+ mService.sendNotification(mServerIf, device.getAddress(),
+ service.getType(), service.getInstanceId(),
+ new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+ new ParcelUuid(characteristic.getUuid()), confirm,
+ characteristic.getValue());
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a service to the list of services to be hosted.
+ *
+ * <p>Once a service has been addded to the the list, the service and it's
+ * included characteristics will be provided by the local device.
+ *
+ * <p>If the local device has already exposed services when this function
+ * is called, a service update notification will be sent to all clients.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param service Service to be added to the list of services provided
+ * by this device.
+ * @return true, if the service has been added successfully
+ */
+ public boolean addService(BluetoothGattService service) {
+ if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid());
+ if (mService == null || mServerIf == 0) return false;
+
+ mServices.add(service);
+
+ try {
+ mService.beginServiceDeclaration(mServerIf, service.getType(),
+ service.getInstanceId(), service.getHandles(),
+ new ParcelUuid(service.getUuid()));
+
+ List<BluetoothGattService> includedServices = service.getIncludedServices();
+ for (BluetoothGattService includedService : includedServices) {
+ mService.addIncludedService(mServerIf,
+ includedService.getType(),
+ includedService.getInstanceId(),
+ new ParcelUuid(includedService.getUuid()));
+ }
+
+ List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
+ for (BluetoothGattCharacteristic characteristic : characteristics) {
+ int permission = ((characteristic.getKeySize() - 7) << 12)
+ + characteristic.getPermissions();
+ mService.addCharacteristic(mServerIf,
+ new ParcelUuid(characteristic.getUuid()),
+ characteristic.getProperties(), permission);
+
+ List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
+ for (BluetoothGattDescriptor descriptor: descriptors) {
+ permission = ((characteristic.getKeySize() - 7) << 12)
+ + descriptor.getPermissions();
+ mService.addDescriptor(mServerIf,
+ new ParcelUuid(descriptor.getUuid()), permission);
+ }
+ }
+
+ mService.endServiceDeclaration(mServerIf);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Removes a service from the list of services to be provided.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param service Service to be removed.
+ * @return true, if the service has been removed
+ */
+ public boolean removeService(BluetoothGattService service) {
+ if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid());
+ if (mService == null || mServerIf == 0) return false;
+
+ BluetoothGattService intService = getService(service.getUuid(),
+ service.getInstanceId(), service.getType());
+ if (intService == null) return false;
+
+ try {
+ mService.removeService(mServerIf, service.getType(),
+ service.getInstanceId(), new ParcelUuid(service.getUuid()));
+ mServices.remove(intService);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Remove all services from the list of provided services.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ */
+ public void clearServices() {
+ if (DBG) Log.d(TAG, "clearServices()");
+ if (mService == null || mServerIf == 0) return;
+
+ try {
+ mService.clearServices(mServerIf);
+ mServices.clear();
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+ }
+
+ /**
+ * Returns a list of GATT services offered by this device.
+ *
+ * <p>An application must call {@link #addService} to add a serice to the
+ * list of services offered by this device.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @return List of services. Returns an empty list
+ * if no services have been added yet.
+ */
+ public List<BluetoothGattService> getServices() {
+ return mServices;
+ }
+
+ /**
+ * Returns a {@link BluetoothGattService} from the list of services offered
+ * by this device.
+ *
+ * <p>If multiple instances of the same service (as identified by UUID)
+ * exist, the first instance of the service is returned.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param uuid UUID of the requested service
+ * @return BluetoothGattService if supported, or null if the requested
+ * service is not offered by this device.
+ */
+ public BluetoothGattService getService(UUID uuid) {
+ for (BluetoothGattService service : mServices) {
+ if (service.getUuid().equals(uuid)) {
+ return service;
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
+ * with {@link BluetoothProfile#GATT} as argument
+ *
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public int getConnectionState(BluetoothDevice device) {
+ throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
+ }
+
+ /**
+ * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
+ * with {@link BluetoothProfile#GATT} as argument
+ *
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public List<BluetoothDevice> getConnectedDevices() {
+ throw new UnsupportedOperationException
+ ("Use BluetoothManager#getConnectedDevices instead.");
+ }
+
+ /**
+ * Not supported - please use
+ * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
+ * with {@link BluetoothProfile#GATT} as first argument
+ *
+ * @throws UnsupportedOperationException
+ */
+ @Override
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ throw new UnsupportedOperationException
+ ("Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattServerCallback.java b/core/java/android/bluetooth/BluetoothGattServerCallback.java
new file mode 100644
index 0000000..f9f1d97
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+import android.util.Log;
+
+/**
+ * This abstract class is used to implement {@link BluetoothGattServer} callbacks.
+ */
+public abstract class BluetoothGattServerCallback {
+
+ /**
+ * Callback indicating when a remote device has been connected or disconnected.
+ *
+ * @param device Remote device that has been connected or disconnected.
+ * @param status Status of the connect or disconnect operation.
+ * @param newState Returns the new connection state. Can be one of
+ * {@link BluetoothProfile#STATE_DISCONNECTED} or
+ * {@link BluetoothProfile#STATE_CONNECTED}
+ */
+ public void onConnectionStateChange(BluetoothDevice device, int status,
+ int newState) {
+ }
+
+ /**
+ * Indicates whether a local service has been added successfully.
+ *
+ * @param status Returns {@link BluetoothGatt#GATT_SUCCESS} if the service
+ * was added successfully.
+ * @param service The service that has been added
+ */
+ public void onServiceAdded(int status, BluetoothGattService service) {
+ }
+
+ /**
+ * A remote client has requested to read a local characteristic.
+ *
+ * <p>An application must call {@link BluetoothGattServer#sendResponse}
+ * to complete the request.
+ *
+ * @param device The remote device that has requested the read operation
+ * @param requestId The Id of the request
+ * @param offset Offset into the value of the characteristic
+ * @param characteristic Characteristic to be read
+ */
+ public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
+ int offset, BluetoothGattCharacteristic characteristic) {
+ }
+
+ /**
+ * A remote client has requested to write to a local characteristic.
+ *
+ * <p>An application must call {@link BluetoothGattServer#sendResponse}
+ * to complete the request.
+ *
+ * @param device The remote device that has requested the write operation
+ * @param requestId The Id of the request
+ * @param characteristic Characteristic to be written to.
+ * @param preparedWrite true, if this write operation should be queued for
+ * later execution.
+ * @param responseNeeded true, if the remote device requires a response
+ * @param offset The offset given for the value
+ * @param value The value the client wants to assign to the characteristic
+ */
+ public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
+ BluetoothGattCharacteristic characteristic,
+ boolean preparedWrite, boolean responseNeeded,
+ int offset, byte[] value) {
+ }
+
+ /**
+ * A remote client has requested to read a local descriptor.
+ *
+ * <p>An application must call {@link BluetoothGattServer#sendResponse}
+ * to complete the request.
+ *
+ * @param device The remote device that has requested the read operation
+ * @param requestId The Id of the request
+ * @param offset Offset into the value of the characteristic
+ * @param descriptor Descriptor to be read
+ */
+ public void onDescriptorReadRequest(BluetoothDevice device, int requestId,
+ int offset, BluetoothGattDescriptor descriptor) {
+ }
+
+ /**
+ * A remote client has requested to write to a local descriptor.
+ *
+ * <p>An application must call {@link BluetoothGattServer#sendResponse}
+ * to complete the request.
+ *
+ * @param device The remote device that has requested the write operation
+ * @param requestId The Id of the request
+ * @param descriptor Descriptor to be written to.
+ * @param preparedWrite true, if this write operation should be queued for
+ * later execution.
+ * @param responseNeeded true, if the remote device requires a response
+ * @param offset The offset given for the value
+ * @param value The value the client wants to assign to the descriptor
+ */
+ public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
+ BluetoothGattDescriptor descriptor,
+ boolean preparedWrite, boolean responseNeeded,
+ int offset, byte[] value) {
+ }
+
+ /**
+ * Execute all pending write operations for this device.
+ *
+ * <p>An application must call {@link BluetoothGattServer#sendResponse}
+ * to complete the request.
+ *
+ * @param device The remote device that has requested the write operations
+ * @param requestId The Id of the request
+ * @param execute Whether the pending writes should be executed (true) or
+ * cancelled (false)
+ */
+ public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java
new file mode 100644
index 0000000..39a435b
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGattService.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth GATT Service
+ *
+ * <p> Gatt Service contains a collection of {@link BluetoothGattCharacteristic},
+ * as well as referenced services.
+ */
+public class BluetoothGattService {
+
+ /**
+ * Primary service
+ */
+ public static final int SERVICE_TYPE_PRIMARY = 0;
+
+ /**
+ * Secondary service (included by primary services)
+ */
+ public static final int SERVICE_TYPE_SECONDARY = 1;
+
+
+ /**
+ * The remote device his service is associated with.
+ * This applies to client applications only.
+ * @hide
+ */
+ protected BluetoothDevice mDevice;
+
+ /**
+ * The UUID of this service.
+ * @hide
+ */
+ protected UUID mUuid;
+
+ /**
+ * Instance ID for this service.
+ * @hide
+ */
+ protected int mInstanceId;
+
+ /**
+ * Handle counter override (for conformance testing).
+ * @hide
+ */
+ protected int mHandles = 0;
+
+ /**
+ * Service type (Primary/Secondary).
+ * @hide
+ */
+ protected int mServiceType;
+
+ /**
+ * List of characteristics included in this service.
+ */
+ protected List<BluetoothGattCharacteristic> mCharacteristics;
+
+ /**
+ * List of included services for this service.
+ */
+ protected List<BluetoothGattService> mIncludedServices;
+
+ /**
+ * Create a new BluetoothGattService.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param uuid The UUID for this service
+ * @param serviceType The type of this service,
+ * {@link BluetoothGattService#SERVICE_TYPE_PRIMARY} or
+ * {@link BluetoothGattService#SERVICE_TYPE_SECONDARY}
+ */
+ public BluetoothGattService(UUID uuid, int serviceType) {
+ mDevice = null;
+ mUuid = uuid;
+ mInstanceId = 0;
+ mServiceType = serviceType;
+ mCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+ mIncludedServices = new ArrayList<BluetoothGattService>();
+ }
+
+ /**
+ * Create a new BluetoothGattService
+ * @hide
+ */
+ /*package*/ BluetoothGattService(BluetoothDevice device, UUID uuid,
+ int instanceId, int serviceType) {
+ mDevice = device;
+ mUuid = uuid;
+ mInstanceId = instanceId;
+ mServiceType = serviceType;
+ mCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+ mIncludedServices = new ArrayList<BluetoothGattService>();
+ }
+
+ /**
+ * Returns the device associated with this service.
+ * @hide
+ */
+ /*package*/ BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ /**
+ * Add an included service to this service.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param service The service to be added
+ * @return true, if the included service was added to the service
+ */
+ public boolean addService(BluetoothGattService service) {
+ mIncludedServices.add(service);
+ return true;
+ }
+
+ /**
+ * Add a characteristic to this service.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param characteristic The characteristics to be added
+ * @return true, if the characteristic was added to the service
+ */
+ public boolean addCharacteristic(BluetoothGattCharacteristic characteristic) {
+ mCharacteristics.add(characteristic);
+ characteristic.setService(this);
+ return true;
+ }
+
+ /**
+ * Get characteristic by UUID and instanceId.
+ * @hide
+ */
+ /*package*/ BluetoothGattCharacteristic getCharacteristic(UUID uuid, int instanceId) {
+ for(BluetoothGattCharacteristic characteristic : mCharacteristics) {
+ if (uuid.equals(characteristic.getUuid()) &&
+ mInstanceId == instanceId)
+ return characteristic;
+ }
+ return null;
+ }
+
+ /**
+ * Force the instance ID.
+ * This is needed for conformance testing only.
+ * @hide
+ */
+ public void setInstanceId(int instanceId) {
+ mInstanceId = instanceId;
+ }
+
+ /**
+ * Get the handle count override (conformance testing.
+ * @hide
+ */
+ /*package*/ int getHandles() {
+ return mHandles;
+ }
+
+ /**
+ * Force the number of handles to reserve for this service.
+ * This is needed for conformance testing only.
+ * @hide
+ */
+ public void setHandles(int handles) {
+ mHandles = handles;
+ }
+
+ /**
+ * Add an included service to the internal map.
+ * @hide
+ */
+ /*package*/ void addIncludedService(BluetoothGattService includedService) {
+ mIncludedServices.add(includedService);
+ }
+
+ /**
+ * Returns the UUID of this service
+ *
+ * @return UUID of this service
+ */
+ public UUID getUuid() {
+ return mUuid;
+ }
+
+ /**
+ * Returns the instance ID for this service
+ *
+ * <p>If a remote device offers multiple services with the same UUID
+ * (ex. multiple battery services for different batteries), the instance
+ * ID is used to distuinguish services.
+ *
+ * @return Instance ID of this service
+ */
+ public int getInstanceId() {
+ return mInstanceId;
+ }
+
+ /**
+ * Get the type of this service (primary/secondary)
+ */
+ public int getType() {
+ return mServiceType;
+ }
+
+ /**
+ * Get the list of included GATT services for this service.
+ *
+ * @return List of included services or empty list if no included services
+ * were discovered.
+ */
+ public List<BluetoothGattService> getIncludedServices() {
+ return mIncludedServices;
+ }
+
+ /**
+ * Returns a list of characteristics included in this service.
+ *
+ * @return Characteristics included in this service
+ */
+ public List<BluetoothGattCharacteristic> getCharacteristics() {
+ return mCharacteristics;
+ }
+
+ /**
+ * Returns a characteristic with a given UUID out of the list of
+ * characteristics offered by this service.
+ *
+ * <p>This is a convenience function to allow access to a given characteristic
+ * without enumerating over the list returned by {@link #getCharacteristics}
+ * manually.
+ *
+ * <p>If a remote service offers multiple characteristics with the same
+ * UUID, the first instance of a characteristic with the given UUID
+ * is returned.
+ *
+ * @return GATT characteristic object or null if no characteristic with the
+ * given UUID was found.
+ */
+ public BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
+ for(BluetoothGattCharacteristic characteristic : mCharacteristics) {
+ if (uuid.equals(characteristic.getUuid()))
+ return characteristic;
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
new file mode 100644
index 0000000..172f3bc
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * High level manager used to obtain an instance of an {@link BluetoothAdapter}
+ * and to conduct overall Bluetooth Management.
+ * <p>
+ * Use {@link android.content.Context#getSystemService(java.lang.String)}
+ * with {@link Context#BLUETOOTH_SERVICE} to create an {@link BluetoothManager},
+ * then call {@link #getAdapter} to obtain the {@link BluetoothAdapter}.
+ * <p>
+ * Alternately, you can just call the static helper
+ * {@link BluetoothAdapter#getDefaultAdapter()}.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using BLUETOOTH, read the
+ * <a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer guide.</p>
+ * </div>
+ *
+ * @see Context#getSystemService
+ * @see BluetoothAdapter#getDefaultAdapter()
+ */
+public final class BluetoothManager {
+ private static final String TAG = "BluetoothManager";
+ private static final boolean DBG = true;
+ private static final boolean VDBG = true;
+
+ private final BluetoothAdapter mAdapter;
+
+ /**
+ * @hide
+ */
+ public BluetoothManager(Context context) {
+ context = context.getApplicationContext();
+ if (context == null) {
+ throw new IllegalArgumentException(
+ "context not associated with any application (using a mock context?)");
+ }
+ // Legacy api - getDefaultAdapter does not take in the context
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ }
+
+ /**
+ * Get the default BLUETOOTH Adapter for this device.
+ *
+ * @return the default BLUETOOTH Adapter
+ */
+ public BluetoothAdapter getAdapter() {
+ return mAdapter;
+ }
+
+ /**
+ * Get the current connection state of the profile to the remote device.
+ *
+ * <p>This is not specific to any application configuration but represents
+ * the connection state of the local Bluetooth adapter for certain profile.
+ * This can be used by applications like status bar which would just like
+ * to know the state of Bluetooth.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device Remote bluetooth device.
+ * @param profile GATT or GATT_SERVER
+ * @return State of the profile connection. One of
+ * {@link BluetoothProfile#STATE_CONNECTED}, {@link BluetoothProfile#STATE_CONNECTING},
+ * {@link BluetoothProfile#STATE_DISCONNECTED},
+ * {@link BluetoothProfile#STATE_DISCONNECTING}
+ */
+ public int getConnectionState(BluetoothDevice device, int profile) {
+ if (DBG) Log.d(TAG,"getConnectionState()");
+
+ List<BluetoothDevice> connectedDevices = getConnectedDevices(profile);
+ for(BluetoothDevice connectedDevice : connectedDevices) {
+ if (device.equals(connectedDevice)) {
+ return BluetoothProfile.STATE_CONNECTED;
+ }
+ }
+
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ /**
+ * Get connected devices for the specified profile.
+ *
+ * <p> Return the set of devices which are in state {@link BluetoothProfile#STATE_CONNECTED}
+ *
+ * <p>This is not specific to any application configuration but represents
+ * the connection state of Bluetooth for this profile.
+ * This can be used by applications like status bar which would just like
+ * to know the state of Bluetooth.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param profile GATT or GATT_SERVER
+ * @return List of devices. The list will be empty on error.
+ */
+ public List<BluetoothDevice> getConnectedDevices(int profile) {
+ if (DBG) Log.d(TAG,"getConnectedDevices");
+ if (profile != BluetoothProfile.GATT && profile != BluetoothProfile.GATT_SERVER) {
+ throw new IllegalArgumentException("Profile not supported: " + profile);
+ }
+
+ List<BluetoothDevice> connectedDevices = new ArrayList<BluetoothDevice>();
+
+ try {
+ IBluetoothManager managerService = mAdapter.getBluetoothManager();
+ IBluetoothGatt iGatt = managerService.getBluetoothGatt();
+ if (iGatt == null) return connectedDevices;
+
+ connectedDevices = iGatt.getDevicesMatchingConnectionStates(
+ new int[] { BluetoothProfile.STATE_CONNECTED });
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+
+ return connectedDevices;
+ }
+
+ /**
+ *
+ * Get a list of devices that match any of the given connection
+ * states.
+ *
+ * <p> If none of the devices match any of the given states,
+ * an empty list will be returned.
+ *
+ * <p>This is not specific to any application configuration but represents
+ * the connection state of the local Bluetooth adapter for this profile.
+ * This can be used by applications like status bar which would just like
+ * to know the state of the local adapter.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param profile GATT or GATT_SERVER
+ * @param states Array of states. States can be one of
+ * {@link BluetoothProfile#STATE_CONNECTED}, {@link BluetoothProfile#STATE_CONNECTING},
+ * {@link BluetoothProfile#STATE_DISCONNECTED},
+ * {@link BluetoothProfile#STATE_DISCONNECTING},
+ * @return List of devices. The list will be empty on error.
+ */
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int profile, int[] states) {
+ if (DBG) Log.d(TAG,"getDevicesMatchingConnectionStates");
+
+ if (profile != BluetoothProfile.GATT && profile != BluetoothProfile.GATT_SERVER) {
+ throw new IllegalArgumentException("Profile not supported: " + profile);
+ }
+
+ List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+
+ try {
+ IBluetoothManager managerService = mAdapter.getBluetoothManager();
+ IBluetoothGatt iGatt = managerService.getBluetoothGatt();
+ if (iGatt == null) return devices;
+ devices = iGatt.getDevicesMatchingConnectionStates(states);
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ }
+
+ return devices;
+ }
+
+ /**
+ * Open a GATT Server
+ * The callback is used to deliver results to Caller, such as connection status as well
+ * as the results of any other GATT server operations.
+ * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer
+ * to conduct GATT server operations.
+ * @param context App context
+ * @param callback GATT server callback handler that will receive asynchronous callbacks.
+ * @return BluetoothGattServer instance
+ */
+ public BluetoothGattServer openGattServer(Context context,
+ BluetoothGattServerCallback callback) {
+ if (context == null || callback == null) {
+ throw new IllegalArgumentException("null parameter: " + context + " " + callback);
+ }
+
+ // TODO(Bluetooth) check whether platform support BLE
+ // Do the check here or in GattServer?
+
+ try {
+ IBluetoothManager managerService = mAdapter.getBluetoothManager();
+ IBluetoothGatt iGatt = managerService.getBluetoothGatt();
+ if (iGatt == null) {
+ Log.e(TAG, "Fail to get GATT Server connection");
+ return null;
+ }
+ BluetoothGattServer mGattServer = new BluetoothGattServer(context, iGatt);
+ Boolean regStatus = mGattServer.registerCallback(callback);
+ return regStatus? mGattServer : null;
+ } catch (RemoteException e) {
+ Log.e(TAG,"",e);
+ return null;
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothOutputStream.java b/core/java/android/bluetooth/BluetoothOutputStream.java
index 62242a2..117dd47 100644
--- a/core/java/android/bluetooth/BluetoothOutputStream.java
+++ b/core/java/android/bluetooth/BluetoothOutputStream.java
@@ -84,4 +84,15 @@ import java.io.OutputStream;
}
mSocket.write(b, offset, count);
}
+ /**
+ * Wait until the data in sending queue is emptied. A polling version
+ * for flush implementation. Use it to ensure the writing data afterwards will
+ * be packed in the new RFCOMM frame.
+ * @throws IOException
+ * if an i/o error occurs.
+ * @since Android 4.2.3
+ */
+ public void flush() throws IOException {
+ mSocket.flush();
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 1920efa..43079f4 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -88,6 +88,16 @@ public interface BluetoothProfile {
public static final int PBAP = 6;
/**
+ * GATT
+ */
+ static public final int GATT = 7;
+
+ /**
+ * GATT_SERVER
+ */
+ static public final int GATT_SERVER = 8;
+
+ /**
* Default priority for devices that we try to auto-connect to and
* and allow incoming connections for the profile
* @hide
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 8029a1a..a19341c 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -192,6 +192,7 @@ public final class BluetoothSocket implements Closeable {
if (VDBG) 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);
+ as.close();
throw new IOException("bt socket acept failed");
}
as.mSocket = new LocalSocket(fds[0]);
@@ -407,6 +408,17 @@ public final class BluetoothSocket implements Closeable {
if (VDBG) Log.d(TAG, "available: " + mSocketIS);
return mSocketIS.available();
}
+ /**
+ * Wait until the data in sending queue is emptied. A polling version
+ * for flush implementation. Used to ensure the writing data afterwards will
+ * be packed in new RFCOMM frame.
+ * @throws IOException
+ * if an i/o error occurs.
+ */
+ /*package*/ void flush() throws IOException {
+ if (VDBG) Log.d(TAG, "flush: " + mSocketOS);
+ mSocketOS.flush();
+ }
/*package*/ int read(byte[] b, int offset, int length) throws IOException {
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
index 063e5a8..0aedecb 100644
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -21,7 +21,7 @@ import android.os.ServiceManager;
import android.os.INetworkManagementService;
import android.content.Context;
import android.net.ConnectivityManager;
-import android.net.DhcpInfoInternal;
+import android.net.DhcpResults;
import android.net.LinkCapabilities;
import android.net.LinkProperties;
import android.net.NetworkInfo;
@@ -29,7 +29,10 @@ import android.net.NetworkInfo.DetailedState;
import android.net.NetworkStateTracker;
import android.net.NetworkUtils;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
+import android.os.Messenger;
+import android.text.TextUtils;
import android.util.Log;
import java.net.InterfaceAddress;
import android.net.LinkAddress;
@@ -37,8 +40,11 @@ import android.net.RouteInfo;
import java.net.Inet4Address;
import android.os.SystemProperties;
+import com.android.internal.util.AsyncChannel;
+
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
/**
* This class tracks the data connection associated with Bluetooth
@@ -52,24 +58,29 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker {
private static final String NETWORKTYPE = "BLUETOOTH_TETHER";
private static final String TAG = "BluetoothTethering";
private static final boolean DBG = true;
- private static final boolean VDBG = false;
+ private static final boolean VDBG = true;
private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0);
private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
+ private final Object mLinkPropertiesLock = new Object();
private LinkProperties mLinkProperties;
+
private LinkCapabilities mLinkCapabilities;
+
+ private final Object mNetworkInfoLock = new Object();
private NetworkInfo mNetworkInfo;
private BluetoothPan mBluetoothPan;
- private static String mIface;
- private Thread mDhcpThread;
+ private static String mRevTetheredIface;
/* For sending events to connectivity service handler */
private Handler mCsHandler;
- private Context mContext;
- public static BluetoothTetheringDataTracker sInstance;
+ protected Context mContext;
+ private static BluetoothTetheringDataTracker sInstance;
+ private BtdtHandler mBtdtHandler;
+ private AtomicReference<AsyncChannel> mAsyncChannel = new AtomicReference<AsyncChannel>(null);
private BluetoothTetheringDataTracker() {
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_BLUETOOTH, 0, NETWORKTYPE, "");
@@ -109,6 +120,7 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker {
if (adapter != null) {
adapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.PAN);
}
+ mBtdtHandler = new BtdtHandler(target.getLooper(), this);
}
private BluetoothProfile.ServiceListener mProfileServiceListener =
@@ -140,6 +152,11 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker {
// not implemented
}
+ @Override
+ public void captivePortalCheckCompleted(boolean isCaptivePortal) {
+ // not implemented
+ }
+
/**
* Re-enable connectivity to a network after a {@link #teardown()}.
*/
@@ -225,15 +242,19 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker {
/**
* Fetch NetworkInfo for the network
*/
- public synchronized NetworkInfo getNetworkInfo() {
- return mNetworkInfo;
+ public NetworkInfo getNetworkInfo() {
+ synchronized (mNetworkInfoLock) {
+ return new NetworkInfo(mNetworkInfo);
+ }
}
/**
* Fetch LinkProperties for the network
*/
- public synchronized LinkProperties getLinkProperties() {
- return new LinkProperties(mLinkProperties);
+ public LinkProperties getLinkProperties() {
+ synchronized (mLinkPropertiesLock) {
+ return new LinkProperties(mLinkProperties);
+ }
}
/**
@@ -287,91 +308,131 @@ public class BluetoothTetheringDataTracker implements NetworkStateTracker {
return count;
}
-
- 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;
+ void startReverseTether(final LinkProperties linkProperties) {
+ if (linkProperties == null || TextUtils.isEmpty(linkProperties.getInterfaceName())) {
+ Log.e(TAG, "attempted to reverse tether with empty interface");
+ return;
}
- 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;
- if (DBG) Log.d(TAG, "startReverseTether mCsHandler: " + mCsHandler);
- mDhcpThread = new Thread(new Runnable() {
+ synchronized (mLinkPropertiesLock) {
+ if (mLinkProperties.getInterfaceName() != null) {
+ Log.e(TAG, "attempted to reverse tether while already in process");
+ return;
+ }
+ mLinkProperties = linkProperties;
+ }
+ Thread dhcpThread = new Thread(new Runnable() {
public void run() {
- //TODO(): Add callbacks for failure and success case.
//Currently this thread runs independently.
- if (DBG) Log.d(TAG, "startReverseTether mCsHandler: " + mCsHandler);
- String DhcpResultName = "dhcp." + mIface + ".result";;
- String result = "";
- if (VDBG) 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);
- if (VDBG) Log.d(TAG, "read " + DhcpResultName + ": " + result);
- if(result.equals("failed")) {
- Log.e(TAG, "startReverseTether, failed to start dhcp service");
+ DhcpResults dhcpResults = new DhcpResults();
+ boolean success = NetworkUtils.runDhcp(linkProperties.getInterfaceName(),
+ dhcpResults);
+ synchronized (mLinkPropertiesLock) {
+ if (linkProperties.getInterfaceName() != mLinkProperties.getInterfaceName()) {
+ Log.e(TAG, "obsolete DHCP run aborted");
return;
}
- if(result.equals("ok")) {
- if (VDBG) Log.d(TAG, "startReverseTether, dhcp resut: " + result);
- if(readLinkProperty(mIface)) {
-
- mNetworkInfo.setIsAvailable(true);
- mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
-
- if (VDBG) 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();
- }
- }
+ if (!success) {
+ Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
return;
}
+ mLinkProperties = dhcpResults.linkProperties;
+ synchronized (mNetworkInfoLock) {
+ mNetworkInfo.setIsAvailable(true);
+ mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
+ if (mCsHandler != null) {
+ Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED,
+ new NetworkInfo(mNetworkInfo));
+ msg.sendToTarget();
+ }
+ }
+ return;
}
- Log.e(TAG, "startReverseTether, dhcp failed, resut: " + result);
}
});
- mDhcpThread.start();
+ dhcpThread.start();
}
- public synchronized void stopReverseTether() {
- //NetworkUtils.stopDhcp(iface);
- if(mDhcpThread != null && mDhcpThread.isAlive()) {
- mDhcpThread.interrupt();
- try { mDhcpThread.join(); } catch (InterruptedException ie) { return; }
+ void stopReverseTether() {
+ synchronized (mLinkPropertiesLock) {
+ if (TextUtils.isEmpty(mLinkProperties.getInterfaceName())) {
+ Log.e(TAG, "attempted to stop reverse tether with nothing tethered");
+ return;
+ }
+ NetworkUtils.stopDhcp(mLinkProperties.getInterfaceName());
+ mLinkProperties.clear();
+ synchronized (mNetworkInfoLock) {
+ mNetworkInfo.setIsAvailable(false);
+ mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
+
+ if (mCsHandler != null) {
+ mCsHandler.obtainMessage(EVENT_STATE_CHANGED, new NetworkInfo(mNetworkInfo)).
+ sendToTarget();
+ }
+ }
}
- mLinkProperties.clear();
- mNetworkInfo.setIsAvailable(false);
- mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
-
- Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
- msg.sendToTarget();
-
- msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
- msg.sendToTarget();
}
public void setDependencyMet(boolean met) {
// not supported on this network
}
+
+ @Override
+ public void addStackedLink(LinkProperties link) {
+ mLinkProperties.addStackedLink(link);
+ }
+
+ @Override
+ public void removeStackedLink(LinkProperties link) {
+ mLinkProperties.removeStackedLink(link);
+ }
+
+ static class BtdtHandler extends Handler {
+ private AsyncChannel mStackChannel;
+ private final BluetoothTetheringDataTracker mBtdt;
+
+ BtdtHandler(Looper looper, BluetoothTetheringDataTracker parent) {
+ super(looper);
+ mBtdt = parent;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+ if (VDBG) Log.d(TAG, "got CMD_CHANNEL_HALF_CONNECTED");
+ if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+ AsyncChannel ac = (AsyncChannel)msg.obj;
+ if (mBtdt.mAsyncChannel.compareAndSet(null, ac) == false) {
+ Log.e(TAG, "Trying to set mAsyncChannel twice!");
+ } else {
+ ac.sendMessage(
+ AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+ }
+ }
+ break;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+ if (VDBG) Log.d(TAG, "got CMD_CHANNEL_DISCONNECTED");
+ mBtdt.stopReverseTether();
+ mBtdt.mAsyncChannel.set(null);
+ break;
+ case NetworkStateTracker.EVENT_NETWORK_CONNECTED:
+ LinkProperties linkProperties = (LinkProperties)(msg.obj);
+ if (VDBG) Log.d(TAG, "got EVENT_NETWORK_CONNECTED, " + linkProperties);
+ mBtdt.startReverseTether(linkProperties);
+ break;
+ case NetworkStateTracker.EVENT_NETWORK_DISCONNECTED:
+ linkProperties = (LinkProperties)(msg.obj);
+ if (VDBG) Log.d(TAG, "got EVENT_NETWORK_DISCONNECTED, " + linkProperties);
+ mBtdt.stopReverseTether();
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void supplyMessenger(Messenger messenger) {
+ if (messenger != null) {
+ new AsyncChannel().connect(mContext, mBtdtHandler, messenger);
+ }
+ }
}
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index d016c26..80806f9 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -60,6 +60,7 @@ interface IBluetooth
int getBondState(in BluetoothDevice device);
String getRemoteName(in BluetoothDevice device);
+ int getRemoteType(in BluetoothDevice device);
String getRemoteAlias(in BluetoothDevice device);
boolean setRemoteAlias(in BluetoothDevice device, in String name);
int getRemoteClass(in BluetoothDevice device);
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
new file mode 100644
index 0000000..c89d132
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.ParcelUuid;
+
+import android.bluetooth.IBluetoothGattCallback;
+import android.bluetooth.IBluetoothGattServerCallback;
+
+/**
+ * API for interacting with BLE / GATT
+ * @hide
+ */
+interface IBluetoothGatt {
+ List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
+
+ void startScan(in int appIf, in boolean isServer);
+ void startScanWithUuids(in int appIf, in boolean isServer, in ParcelUuid[] ids);
+ void stopScan(in int appIf, in boolean isServer);
+
+ void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
+ void unregisterClient(in int clientIf);
+ void clientConnect(in int clientIf, in String address, in boolean isDirect);
+ void clientDisconnect(in int clientIf, in String address);
+ void refreshDevice(in int clientIf, in String address);
+ void discoverServices(in int clientIf, in String address);
+ void readCharacteristic(in int clientIf, in String address, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId,
+ in int charInstanceId, in ParcelUuid charId,
+ in int authReq);
+ void writeCharacteristic(in int clientIf, in String address, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId,
+ in int charInstanceId, in ParcelUuid charId,
+ in int writeType, in int authReq, in byte[] value);
+ void readDescriptor(in int clientIf, in String address, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId,
+ in int charInstanceId, in ParcelUuid charId,
+ in ParcelUuid descrUuid, in int authReq);
+ void writeDescriptor(in int clientIf, in String address, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId,
+ in int charInstanceId, in ParcelUuid charId,
+ in ParcelUuid descrId, in int writeType,
+ in int authReq, in byte[] value);
+ void registerForNotification(in int clientIf, in String address, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId,
+ in int charInstanceId, in ParcelUuid charId,
+ in boolean enable);
+ void beginReliableWrite(in int clientIf, in String address);
+ void endReliableWrite(in int clientIf, in String address, in boolean execute);
+ void readRemoteRssi(in int clientIf, in String address);
+
+ void registerServer(in ParcelUuid appId, in IBluetoothGattServerCallback callback);
+ void unregisterServer(in int serverIf);
+ void serverConnect(in int servertIf, in String address, in boolean isDirect);
+ void serverDisconnect(in int serverIf, in String address);
+ void beginServiceDeclaration(in int serverIf, in int srvcType,
+ in int srvcInstanceId, in int minHandles,
+ in ParcelUuid srvcId);
+ void addIncludedService(in int serverIf, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId);
+ void addCharacteristic(in int serverIf, in ParcelUuid charId,
+ in int properties, in int permissions);
+ void addDescriptor(in int serverIf, in ParcelUuid descId,
+ in int permissions);
+ void endServiceDeclaration(in int serverIf);
+ void removeService(in int serverIf, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId);
+ void clearServices(in int serverIf);
+ void sendResponse(in int serverIf, in String address, in int requestId,
+ in int status, in int offset, in byte[] value);
+ void sendNotification(in int serverIf, in String address, in int srvcType,
+ in int srvcInstanceId, in ParcelUuid srvcId,
+ in int charInstanceId, in ParcelUuid charId,
+ in boolean confirm, in byte[] value);
+}
diff --git a/core/java/android/bluetooth/IBluetoothGattCallback.aidl b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
new file mode 100644
index 0000000..fc52172
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothGattCallback.aidl
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.bluetooth;
+
+import android.os.ParcelUuid;
+
+
+/**
+ * Callback definitions for interacting with BLE / GATT
+ * @hide
+ */
+interface IBluetoothGattCallback {
+ void onClientRegistered(in int status, in int clientIf);
+ void onClientConnectionState(in int status, in int clientIf,
+ in boolean connected, in String address);
+ void onScanResult(in String address, in int rssi, in byte[] advData);
+ void onGetService(in String address, in int srvcType, in int srvcInstId,
+ in ParcelUuid srvcUuid);
+ void onGetIncludedService(in String address, in int srvcType, in int srvcInstId,
+ in ParcelUuid srvcUuid, in int inclSrvcType,
+ in int inclSrvcInstId, in ParcelUuid inclSrvcUuid);
+ void onGetCharacteristic(in String address, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid,
+ in int charProps);
+ void onGetDescriptor(in String address, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid,
+ in ParcelUuid descrUuid);
+ void onSearchComplete(in String address, in int status);
+ void onCharacteristicRead(in String address, in int status, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid,
+ in byte[] value);
+ void onCharacteristicWrite(in String address, in int status, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid);
+ void onExecuteWrite(in String address, in int status);
+ void onDescriptorRead(in String address, in int status, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid,
+ in ParcelUuid descrUuid, in byte[] value);
+ void onDescriptorWrite(in String address, in int status, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid,
+ in ParcelUuid descrUuid);
+ void onNotify(in String address, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcUuid,
+ in int charInstId, in ParcelUuid charUuid,
+ in byte[] value);
+ void onReadRemoteRssi(in String address, in int rssi, in int status);
+}
diff --git a/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
new file mode 100644
index 0000000..ae9bffc
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothGattServerCallback.aidl
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.bluetooth;
+
+import android.os.ParcelUuid;
+
+
+/**
+ * Callback definitions for interacting with BLE / GATT
+ * @hide
+ */
+interface IBluetoothGattServerCallback {
+ void onServerRegistered(in int status, in int serverIf);
+ void onScanResult(in String address, in int rssi, in byte[] advData);
+ void onServerConnectionState(in int status, in int serverIf,
+ in boolean connected, in String address);
+ void onServiceAdded(in int status, in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcId);
+ void onCharacteristicReadRequest(in String address, in int transId,
+ in int offset, in boolean isLong,
+ in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcId,
+ in int charInstId, in ParcelUuid charId);
+ void onDescriptorReadRequest(in String address, in int transId,
+ in int offset, in boolean isLong,
+ in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcId,
+ in int charInstId, in ParcelUuid charId,
+ in ParcelUuid descrId);
+ void onCharacteristicWriteRequest(in String address, in int transId,
+ in int offset, in int length,
+ in boolean isPrep,
+ in boolean needRsp,
+ in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcId,
+ in int charInstId, in ParcelUuid charId,
+ in byte[] value);
+ void onDescriptorWriteRequest(in String address, in int transId,
+ in int offset, in int length,
+ in boolean isPrep,
+ in boolean needRsp,
+ in int srvcType,
+ in int srvcInstId, in ParcelUuid srvcId,
+ in int charInstId, in ParcelUuid charId,
+ in ParcelUuid descrId,
+ in byte[] value);
+ void onExecuteWrite(in String address, in int transId, in boolean execWrite);
+}
diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl
index ed8777c..493d2f8 100644
--- a/core/java/android/bluetooth/IBluetoothManager.aidl
+++ b/core/java/android/bluetooth/IBluetoothManager.aidl
@@ -17,6 +17,7 @@
package android.bluetooth;
import android.bluetooth.IBluetooth;
+import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothManagerCallback;
import android.bluetooth.IBluetoothStateChangeCallback;
@@ -35,6 +36,7 @@ interface IBluetoothManager
boolean enable();
boolean enableNoAutoConnect();
boolean disable(boolean persist);
+ IBluetoothGatt getBluetoothGatt();
String getAddress();
String getName();
diff --git a/core/java/android/bluetooth/package.html b/core/java/android/bluetooth/package.html
index ba75034..200a21b 100644
--- a/core/java/android/bluetooth/package.html
+++ b/core/java/android/bluetooth/package.html
@@ -1,15 +1,20 @@
<HTML>
<BODY>
<p>Provides classes that manage Bluetooth functionality, such as scanning for
-devices, connecting with devices, and managing data transfer between devices.</p>
+devices, connecting with devices, and managing data transfer between devices.
+The Bluetooth API supports both "Classic Bluetooth" and Bluetooth Low Energy.</p>
-<p>For more information, see the
-<a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> guide.</p>
+<p>For more information about Classic Bluetooth, see the
+<a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> guide.
+For more information about Bluetooth Low Energy, see the
+<a href="{@docRoot}guide/topics/connectivity/bluetooth-le.html">
+Bluetooth Low Energy</a> guide.</p>
{@more}
<p>The Bluetooth APIs let applications:</p>
<ul>
- <li>Scan for other Bluetooth devices</li>
+ <li>Scan for other Bluetooth devices (including Bluetooth Low Energy
+ devices)</li>
<li>Query the local Bluetooth adapter for paired Bluetooth devices</li>
<li>Establish RFCOMM channels/sockets</li>
<li>Connect to specified sockets on other devices</li>
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index f9025d9..612c67f 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -78,7 +78,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
// So we treat this case as an unhandled exception.
throw ex;
}
- if (DEBUG) Slog.v(TAG, this + " <<< doInBackground (was canceled)");
+ if (DEBUG) Slog.v(TAG, this + " <<< doInBackground (was canceled)", ex);
return null;
}
}
@@ -231,6 +231,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
onCanceled(data);
if (mCancellingTask == task) {
if (DEBUG) Slog.v(TAG, "Cancelled task is now canceled!");
+ rollbackContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mCancellingTask = null;
if (DEBUG) Slog.v(TAG, "Delivering cancellation");
@@ -248,6 +249,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
// This cursor has been abandoned; just cancel the new data.
onCanceled(data);
} else {
+ commitContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mTask = null;
if (DEBUG) Slog.v(TAG, "Delivering result");
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 4f42d50..9a32fdf 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -520,7 +520,7 @@ public abstract class BroadcastReceiver {
IActivityManager am = ActivityManagerNative.getDefault();
IBinder binder = null;
try {
- service.setAllowFds(false);
+ service.prepareToLeaveProcess();
binder = am.peekService(service, service.resolveTypeIfNeeded(
myContext.getContentResolver()));
} catch (RemoteException e) {
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 88f1a3d..50c4fed 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -21,6 +21,7 @@ import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.StrictMode;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
@@ -790,6 +791,24 @@ public class ClipData implements Parcelable {
return mItems.get(index);
}
+ /**
+ * Prepare this {@link ClipData} to leave an app process.
+ *
+ * @hide
+ */
+ public void prepareToLeaveProcess() {
+ final int size = mItems.size();
+ for (int i = 0; i < size; i++) {
+ final Item item = mItems.get(i);
+ if (item.mIntent != null) {
+ item.mIntent.prepareToLeaveProcess();
+ }
+ if (item.mUri != null && StrictMode.vmFileUriExposureEnabled()) {
+ item.mUri.checkFileUriExposed("ClipData.Item.getUri()");
+ }
+ }
+ }
+
@Override
public String toString() {
StringBuilder b = new StringBuilder(128);
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index dfd1820..69f9d4a 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -22,6 +22,7 @@ import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
import android.os.ServiceManager;
+import android.os.StrictMode;
import android.util.Log;
import java.util.ArrayList;
@@ -118,7 +119,10 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public void setPrimaryClip(ClipData clip) {
try {
- getService().setPrimaryClip(clip);
+ if (clip != null) {
+ clip.prepareToLeaveProcess();
+ }
+ getService().setPrimaryClip(clip, mContext.getBasePackageName());
} catch (RemoteException e) {
}
}
@@ -128,7 +132,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public ClipData getPrimaryClip() {
try {
- return getService().getPrimaryClip(mContext.getPackageName());
+ return getService().getPrimaryClip(mContext.getBasePackageName());
} catch (RemoteException e) {
return null;
}
@@ -140,7 +144,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public ClipDescription getPrimaryClipDescription() {
try {
- return getService().getPrimaryClipDescription();
+ return getService().getPrimaryClipDescription(mContext.getBasePackageName());
} catch (RemoteException e) {
return null;
}
@@ -151,7 +155,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public boolean hasPrimaryClip() {
try {
- return getService().hasPrimaryClip();
+ return getService().hasPrimaryClip(mContext.getBasePackageName());
} catch (RemoteException e) {
return false;
}
@@ -162,7 +166,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
if (mPrimaryClipChangedListeners.size() == 0) {
try {
getService().addPrimaryClipChangedListener(
- mPrimaryClipChangedServiceListener);
+ mPrimaryClipChangedServiceListener, mContext.getBasePackageName());
} catch (RemoteException e) {
}
}
@@ -209,7 +213,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public boolean hasText() {
try {
- return getService().hasClipboardText();
+ return getService().hasClipboardText(mContext.getBasePackageName());
} catch (RemoteException e) {
return false;
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 612ff0b..cf627d7 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -16,8 +16,10 @@
package android.content;
+import static android.content.pm.PackageManager.GET_PROVIDERS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import android.app.AppOpsManager;
import android.content.pm.PackageManager;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
@@ -100,6 +102,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
private String mWritePermission;
private PathPermission[] mPathPermissions;
private boolean mExported;
+ private boolean mNoPerms;
private Transport mTransport = new Transport();
@@ -154,8 +157,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
*
* @param abstractInterface The ContentProvider interface that is to be
* coerced.
- * @return If the IContentProvider is non-null and local, returns its actual
- * ContentProvider instance. Otherwise returns null.
+ * @return If the IContentProvider is non-{@code null} and local, returns its actual
+ * ContentProvider instance. Otherwise returns {@code null}.
* @hide
*/
public static ContentProvider coerceToLocalContentProvider(
@@ -172,6 +175,10 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* @hide
*/
class Transport extends ContentProviderNative {
+ AppOpsManager mAppOpsManager = null;
+ int mReadOp = AppOpsManager.OP_NONE;
+ int mWriteOp = AppOpsManager.OP_NONE;
+
ContentProvider getContentProvider() {
return ContentProvider.this;
}
@@ -182,10 +189,13 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
@Override
- public Cursor query(Uri uri, String[] projection,
+ public Cursor query(String callingPkg, Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
ICancellationSignal cancellationSignal) {
- enforceReadPermission(uri);
+ if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ return rejectQuery(uri, projection, selection, selectionArgs, sortOrder,
+ CancellationSignal.fromTransport(cancellationSignal));
+ }
return ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder,
CancellationSignal.fromTransport(cancellationSignal));
}
@@ -196,64 +206,77 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
@Override
- public Uri insert(Uri uri, ContentValues initialValues) {
- enforceWritePermission(uri);
+ public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
+ if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ return rejectInsert(uri, initialValues);
+ }
return ContentProvider.this.insert(uri, initialValues);
}
@Override
- public int bulkInsert(Uri uri, ContentValues[] initialValues) {
- enforceWritePermission(uri);
+ public int bulkInsert(String callingPkg, Uri uri, ContentValues[] initialValues) {
+ if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ return 0;
+ }
return ContentProvider.this.bulkInsert(uri, initialValues);
}
@Override
- public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ public ContentProviderResult[] applyBatch(String callingPkg,
+ ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
for (ContentProviderOperation operation : operations) {
if (operation.isReadOperation()) {
- enforceReadPermission(operation.getUri());
+ if (enforceReadPermission(callingPkg, operation.getUri())
+ != AppOpsManager.MODE_ALLOWED) {
+ throw new OperationApplicationException("App op not allowed", 0);
+ }
}
if (operation.isWriteOperation()) {
- enforceWritePermission(operation.getUri());
+ if (enforceWritePermission(callingPkg, operation.getUri())
+ != AppOpsManager.MODE_ALLOWED) {
+ throw new OperationApplicationException("App op not allowed", 0);
+ }
}
}
return ContentProvider.this.applyBatch(operations);
}
@Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- enforceWritePermission(uri);
+ public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) {
+ if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ return 0;
+ }
return ContentProvider.this.delete(uri, selection, selectionArgs);
}
@Override
- public int update(Uri uri, ContentValues values, String selection,
+ public int update(String callingPkg, Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
- enforceWritePermission(uri);
+ if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ return 0;
+ }
return ContentProvider.this.update(uri, values, selection, selectionArgs);
}
@Override
- public ParcelFileDescriptor openFile(Uri uri, String mode)
+ public ParcelFileDescriptor openFile(String callingPkg, Uri uri, String mode)
throws FileNotFoundException {
- if (mode != null && mode.indexOf('w') != -1) enforceWritePermission(uri);
- else enforceReadPermission(uri);
+ enforceFilePermission(callingPkg, uri, mode);
return ContentProvider.this.openFile(uri, mode);
}
@Override
- public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ public AssetFileDescriptor openAssetFile(String callingPkg, Uri uri, String mode)
throws FileNotFoundException {
- if (mode != null && mode.indexOf('w') != -1) enforceWritePermission(uri);
- else enforceReadPermission(uri);
+ enforceFilePermission(callingPkg, uri, mode);
return ContentProvider.this.openAssetFile(uri, mode);
}
@Override
- public Bundle call(String method, String arg, Bundle extras) {
- return ContentProvider.this.call(method, arg, extras);
+ public Bundle call(String callingPkg, String method, String arg, Bundle extras) {
+ return ContentProvider.this.callFromPackage(callingPkg, method, arg, extras);
}
@Override
@@ -262,9 +285,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
@Override
- public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeType, Bundle opts)
- throws FileNotFoundException {
- enforceReadPermission(uri);
+ public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
+ Bundle opts) throws FileNotFoundException {
+ enforceFilePermission(callingPkg, uri, "r");
return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts);
}
@@ -273,7 +296,28 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
return CancellationSignal.createTransport();
}
- private void enforceReadPermission(Uri uri) throws SecurityException {
+ private void enforceFilePermission(String callingPkg, Uri uri, String mode)
+ throws FileNotFoundException, SecurityException {
+ if (mode != null && mode.indexOf('w') != -1) {
+ if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ throw new FileNotFoundException("App op not allowed");
+ }
+ } else {
+ if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ throw new FileNotFoundException("App op not allowed");
+ }
+ }
+ }
+
+ private int enforceReadPermission(String callingPkg, Uri uri) throws SecurityException {
+ enforceReadPermissionInner(uri);
+ if (mReadOp != AppOpsManager.OP_NONE) {
+ return mAppOpsManager.noteOp(mReadOp, Binder.getCallingUid(), callingPkg);
+ }
+ return AppOpsManager.MODE_ALLOWED;
+ }
+
+ private void enforceReadPermissionInner(Uri uri) throws SecurityException {
final Context context = getContext();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -334,7 +378,15 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
+ ", uid=" + uid + failReason);
}
- private void enforceWritePermission(Uri uri) throws SecurityException {
+ private int enforceWritePermission(String callingPkg, Uri uri) throws SecurityException {
+ enforceWritePermissionInner(uri);
+ if (mWriteOp != AppOpsManager.OP_NONE) {
+ return mAppOpsManager.noteOp(mWriteOp, Binder.getCallingUid(), callingPkg);
+ }
+ return AppOpsManager.MODE_ALLOWED;
+ }
+
+ private void enforceWritePermissionInner(Uri uri) throws SecurityException {
final Context context = getContext();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -398,7 +450,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
/**
* Retrieves the Context this provider is running in. Only available once
- * {@link #onCreate} has been called -- this will return null in the
+ * {@link #onCreate} has been called -- this will return {@code null} in the
* constructor.
*/
public final Context getContext() {
@@ -471,6 +523,21 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
return mPathPermissions;
}
+ /** @hide */
+ public final void setAppOps(int readOp, int writeOp) {
+ if (!mNoPerms) {
+ mTransport.mAppOpsManager = (AppOpsManager)mContext.getSystemService(
+ Context.APP_OPS_SERVICE);
+ mTransport.mReadOp = readOp;
+ mTransport.mWriteOp = writeOp;
+ }
+ }
+
+ /** @hide */
+ public AppOpsManager getAppOpsManager() {
+ return mTransport.mAppOpsManager;
+ }
+
/**
* Implement this to initialize your content provider on startup.
* This method is called for all registered content providers on the
@@ -526,6 +593,31 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
/**
+ * @hide
+ * Implementation when a caller has performed a query on the content
+ * provider, but that call has been rejected for the operation given
+ * to {@link #setAppOps(int, int)}. The default implementation
+ * rewrites the <var>selection</var> argument to include a condition
+ * that is never true (so will always result in an empty cursor)
+ * and calls through to {@link #query(android.net.Uri, String[], String, String[],
+ * String, android.os.CancellationSignal)} with that.
+ */
+ public Cursor rejectQuery(Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder,
+ CancellationSignal cancellationSignal) {
+ // The read is not allowed... to fake it out, we replace the given
+ // selection statement with a dummy one that will always be false.
+ // This way we will get a cursor back that has the correct structure
+ // but contains no rows.
+ if (selection == null || selection.isEmpty()) {
+ selection = "'A' = 'B'";
+ } else {
+ selection = "'A' = 'B' AND (" + selection + ")";
+ }
+ return query(uri, projection, selection, selectionArgs, sortOrder, cancellationSignal);
+ }
+
+ /**
* Implement this to handle query requests from clients.
* This method can be called from multiple threads, as described in
* <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
@@ -570,15 +662,15 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* that the implementation should parse and add to a WHERE or HAVING clause, specifying
* that _id value.
* @param projection The list of columns to put into the cursor. If
- * null all columns are included.
+ * {@code null} all columns are included.
* @param selection A selection criteria to apply when filtering rows.
- * If null then all rows are included.
+ * If {@code null} then all rows are included.
* @param selectionArgs You may include ?s in selection, which will be replaced by
* the values from selectionArgs, in order that they appear in the selection.
* The values will be bound as Strings.
* @param sortOrder How the rows in the cursor should be sorted.
- * If null then the provider is free to define the sort order.
- * @return a Cursor or null.
+ * If {@code null} then the provider is free to define the sort order.
+ * @return a Cursor or {@code null}.
*/
public abstract Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder);
@@ -633,18 +725,18 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* that the implementation should parse and add to a WHERE or HAVING clause, specifying
* that _id value.
* @param projection The list of columns to put into the cursor. If
- * null all columns are included.
+ * {@code null} all columns are included.
* @param selection A selection criteria to apply when filtering rows.
- * If null then all rows are included.
+ * If {@code null} then all rows are included.
* @param selectionArgs You may include ?s in selection, which will be replaced by
* the values from selectionArgs, in order that they appear in the selection.
* The values will be bound as Strings.
* @param sortOrder How the rows in the cursor should be sorted.
- * If null then the provider is free to define the sort order.
- * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
+ * If {@code null} then the provider is free to define the sort order.
+ * @param cancellationSignal A signal to cancel the operation in progress, or {@code null} if none.
* If the operation is canceled, then {@link OperationCanceledException} will be thrown
* when the query is executed.
- * @return a Cursor or null.
+ * @return a Cursor or {@code null}.
*/
public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
@@ -668,19 +760,37 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* to retrieve the MIME type for a URI when dispatching intents.
*
* @param uri the URI to query.
- * @return a MIME type string, or null if there is no type.
+ * @return a MIME type string, or {@code null} if there is no type.
*/
public abstract String getType(Uri uri);
/**
+ * @hide
+ * Implementation when a caller has performed an insert on the content
+ * provider, but that call has been rejected for the operation given
+ * to {@link #setAppOps(int, int)}. The default implementation simply
+ * returns a dummy URI that is the base URI with a 0 path element
+ * appended.
+ */
+ public Uri rejectInsert(Uri uri, ContentValues values) {
+ // If not allowed, we need to return some reasonable URI. Maybe the
+ // content provider should be responsible for this, but for now we
+ // will just return the base URI with a dummy '0' tagged on to it.
+ // You shouldn't be able to read if you can't write, anyway, so it
+ // shouldn't matter much what is returned.
+ return uri.buildUpon().appendPath("0").build();
+ }
+
+ /**
* Implement this to handle requests to insert a new row.
* As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
* after inserting.
* This method can be called from multiple threads, as described in
* <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
* and Threads</a>.
- * @param uri The content:// URI of the insertion request.
+ * @param uri The content:// URI of the insertion request. This must not be {@code null}.
* @param values A set of column_name/value pairs to add to the database.
+ * This must not be {@code null}.
* @return The URI for the newly inserted item.
*/
public abstract Uri insert(Uri uri, ContentValues values);
@@ -697,6 +807,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
*
* @param uri The content:// URI of the insertion request.
* @param values An array of sets of column_name/value pairs to add to the database.
+ * This must not be {@code null}.
* @return The number of values that were inserted.
*/
public int bulkInsert(Uri uri, ContentValues[] values) {
@@ -741,8 +852,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
*
* @param uri The URI to query. This can potentially have a record ID if this
* is an update request for a specific record.
- * @param values A Bundle mapping from column names to new column values (NULL is a
- * valid value).
+ * @param values A set of column_name/value pairs to update in the database.
+ * This must not be {@code null}.
* @param selection An optional filter to match rows to update.
* @return the number of rows affected.
*/
@@ -764,6 +875,18 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* their responsibility to close it when done. That is, the implementation
* of this method should create a new ParcelFileDescriptor for each call.
*
+ * <p class="note">For use in Intents, you will want to implement {@link #getType}
+ * to return the appropriate MIME type for the data returned here with
+ * the same URI. This will allow intent resolution to automatically determine the data MIME
+ * type and select the appropriate matching targets as part of its operation.</p>
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.
+ * You may also want to support other common columns if you have additional meta-data
+ * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED}
+ * in {@link android.provider.MediaStore.MediaColumns}.</p>
+ *
* @param uri The URI whose file is to be opened.
* @param mode Access mode for the file. May be "r" for read-only access,
* "rw" for read and write access, or "rwt" for read and write access
@@ -779,6 +902,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
*
* @see #openAssetFile(Uri, String)
* @see #openFileHelper(Uri, String)
+ * @see #getType(android.net.Uri)
*/
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
@@ -806,6 +930,15 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with
* applications that can not handle sub-sections of files.</p>
*
+ * <p class="note">For use in Intents, you will want to implement {@link #getType}
+ * to return the appropriate MIME type for the data returned here with
+ * the same URI. This will allow intent resolution to automatically determine the data MIME
+ * type and select the appropriate matching targets as part of its operation.</p>
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.</p>
+ *
* @param uri The URI whose file is to be opened.
* @param mode Access mode for the file. May be "r" for read-only access,
* "w" for write-only access (erasing whatever data is currently in
@@ -823,6 +956,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
*
* @see #openFile(Uri, String)
* @see #openFileHelper(Uri, String)
+ * @see #getType(android.net.Uri)
*/
public AssetFileDescriptor openAssetFile(Uri uri, String mode)
throws FileNotFoundException {
@@ -875,7 +1009,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
/**
* Called by a client to determine the types of data streams that this
* content provider supports for the given URI. The default implementation
- * returns null, meaning no types. If your content provider stores data
+ * returns {@code null}, meaning no types. If your content provider stores data
* of a particular type, return that MIME type if it matches the given
* mimeTypeFilter. If it can perform type conversions, return an array
* of all supported MIME types that match mimeTypeFilter.
@@ -883,7 +1017,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* @param uri The data in the content provider being queried.
* @param mimeTypeFilter The type of data the client desires. May be
* a pattern, such as *\/* to retrieve all possible data types.
- * @return Returns null if there are no possible data streams for the
+ * @return Returns {@code null} if there are no possible data streams for the
* given mimeTypeFilter. Otherwise returns an array of all available
* concrete MIME types.
*
@@ -902,12 +1036,19 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* perform data conversions to generate data of the desired type.
*
* <p>The default implementation compares the given mimeType against the
- * result of {@link #getType(Uri)} and, if the match, simple calls
+ * result of {@link #getType(Uri)} and, if they match, simply calls
* {@link #openAssetFile(Uri, String)}.
*
* <p>See {@link ClipData} for examples of the use and implementation
* of this method.
*
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.
+ * You may also want to support other common columns if you have additional meta-data
+ * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED}
+ * in {@link android.provider.MediaStore.MediaColumns}.</p>
+ *
* @param uri The data in the content provider being queried.
* @param mimeTypeFilter The type of data the client desires. May be
* a pattern, such as *\/*, if the caller does not have specific type
@@ -1029,6 +1170,15 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
/**
+ * Like {@link #attachInfo(Context, android.content.pm.ProviderInfo)}, but for use
+ * when directly instantiating the provider for testing.
+ * @hide
+ */
+ public void attachInfoForTesting(Context context, ProviderInfo info) {
+ attachInfo(context, info, true);
+ }
+
+ /**
* After being instantiated, this is called to tell the content provider
* about itself.
*
@@ -1036,12 +1186,18 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* @param info Registered information about this content provider
*/
public void attachInfo(Context context, ProviderInfo info) {
+ attachInfo(context, info, false);
+ }
+
+ private void attachInfo(Context context, ProviderInfo info, boolean testing) {
/*
* We may be using AsyncTask from binder threads. Make it init here
* so its static handler is on the main thread.
*/
AsyncTask.init();
+ mNoPerms = testing;
+
/*
* Only allow it to be set once, so after the content service gives
* this to us clients can't change it.
@@ -1087,14 +1243,30 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
/**
+ * @hide
+ * Front-end to {@link #call(String, String, android.os.Bundle)} that provides the name
+ * of the calling package.
+ */
+ public Bundle callFromPackage(String callingPackag, String method, String arg, Bundle extras) {
+ return call(method, arg, extras);
+ }
+
+ /**
* Call a provider-defined method. This can be used to implement
* interfaces that are cheaper and/or unnatural for a table-like
* model.
*
- * @param method method name to call. Opaque to framework, but should not be null.
- * @param arg provider-defined String argument. May be null.
- * @param extras provider-defined Bundle argument. May be null.
- * @return provider-defined return value. May be null. Null is also
+ * <p class="note"><strong>WARNING:</strong> The framework does no permission checking
+ * on this entry into the content provider besides the basic ability for the application
+ * to get access to the provider at all. For example, it has no idea whether the call
+ * being executed may read or write data in the provider, so can't enforce those
+ * individual permissions. Any implementation of this method <strong>must</strong>
+ * do its own permission checks on incoming calls to make sure they are allowed.</p>
+ *
+ * @param method method name to call. Opaque to framework, but should not be {@code null}.
+ * @param arg provider-defined String argument. May be {@code null}.
+ * @param extras provider-defined Bundle argument. May be {@code null}.
+ * @return provider-defined return value. May be {@code null}, which is also
* the default for providers which don't implement any call methods.
*/
public Bundle call(String method, String arg, Bundle extras) {
@@ -1132,12 +1304,10 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* Print the Provider's state into the given stream. This gets invoked if
* you run "adb shell dumpsys activity provider &lt;provider_component_name&gt;".
*
- * @param prefix Desired prefix to prepend at each line of output.
* @param fd The raw file descriptor that the dump is being sent to.
* @param writer The PrintWriter to which you should dump your state. This will be
* closed for you after you return.
* @param args additional arguments to the dump request.
- * @hide
*/
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
writer.println("nothing to dump");
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 204f963..8dffac7 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -45,6 +45,7 @@ import java.util.ArrayList;
public class ContentProviderClient {
private final IContentProvider mContentProvider;
private final ContentResolver mContentResolver;
+ private final String mPackageName;
private final boolean mStable;
private boolean mReleased;
@@ -55,6 +56,7 @@ public class ContentProviderClient {
IContentProvider contentProvider, boolean stable) {
mContentProvider = contentProvider;
mContentResolver = contentResolver;
+ mPackageName = contentResolver.mPackageName;
mStable = stable;
}
@@ -81,8 +83,8 @@ public class ContentProviderClient {
cancellationSignal.setRemote(remoteCancellationSignal);
}
try {
- return mContentProvider.query(url, projection, selection, selectionArgs, sortOrder,
- remoteCancellationSignal);
+ return mContentProvider.query(mPackageName, url, projection, selection, selectionArgs,
+ sortOrder, remoteCancellationSignal);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -119,7 +121,7 @@ public class ContentProviderClient {
public Uri insert(Uri url, ContentValues initialValues)
throws RemoteException {
try {
- return mContentProvider.insert(url, initialValues);
+ return mContentProvider.insert(mPackageName, url, initialValues);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -131,7 +133,7 @@ public class ContentProviderClient {
/** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
try {
- return mContentProvider.bulkInsert(url, initialValues);
+ return mContentProvider.bulkInsert(mPackageName, url, initialValues);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -144,7 +146,7 @@ public class ContentProviderClient {
public int delete(Uri url, String selection, String[] selectionArgs)
throws RemoteException {
try {
- return mContentProvider.delete(url, selection, selectionArgs);
+ return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -157,7 +159,7 @@ public class ContentProviderClient {
public int update(Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException {
try {
- return mContentProvider.update(url, values, selection, selectionArgs);
+ return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -176,7 +178,7 @@ public class ContentProviderClient {
public ParcelFileDescriptor openFile(Uri url, String mode)
throws RemoteException, FileNotFoundException {
try {
- return mContentProvider.openFile(url, mode);
+ return mContentProvider.openFile(mPackageName, url, mode);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -195,7 +197,7 @@ public class ContentProviderClient {
public AssetFileDescriptor openAssetFile(Uri url, String mode)
throws RemoteException, FileNotFoundException {
try {
- return mContentProvider.openAssetFile(url, mode);
+ return mContentProvider.openAssetFile(mPackageName, url, mode);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -209,7 +211,7 @@ public class ContentProviderClient {
String mimeType, Bundle opts)
throws RemoteException, FileNotFoundException {
try {
- return mContentProvider.openTypedAssetFile(uri, mimeType, opts);
+ return mContentProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -222,7 +224,7 @@ public class ContentProviderClient {
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
try {
- return mContentProvider.applyBatch(operations);
+ return mContentProvider.applyBatch(mPackageName, operations);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -235,7 +237,7 @@ public class ContentProviderClient {
public Bundle call(String method, String arg, Bundle extras)
throws RemoteException {
try {
- return mContentProvider.call(method, arg, extras);
+ return mContentProvider.call(mPackageName, method, arg, extras);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index 550a1c9..6f822c1 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -81,6 +81,7 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
// String[] projection
@@ -110,16 +111,24 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface(
data.readStrongBinder());
- Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder,
- cancellationSignal);
+ Cursor cursor = query(callingPkg, url, projection, selection, selectionArgs,
+ sortOrder, cancellationSignal);
if (cursor != null) {
- CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor(
- cursor, observer, getProviderName());
- BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
-
- reply.writeNoException();
- reply.writeInt(1);
- d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ try {
+ CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor(
+ cursor, observer, getProviderName());
+ BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
+ cursor = null;
+
+ reply.writeNoException();
+ reply.writeInt(1);
+ d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } finally {
+ // Close cursor if an exception was thrown while constructing the adaptor.
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
} else {
reply.writeNoException();
reply.writeInt(0);
@@ -142,10 +151,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case INSERT_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
ContentValues values = ContentValues.CREATOR.createFromParcel(data);
- Uri out = insert(url, values);
+ Uri out = insert(callingPkg, url, values);
reply.writeNoException();
Uri.writeToParcel(reply, out);
return true;
@@ -154,10 +164,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case BULK_INSERT_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
ContentValues[] values = data.createTypedArray(ContentValues.CREATOR);
- int count = bulkInsert(url, values);
+ int count = bulkInsert(callingPkg, url, values);
reply.writeNoException();
reply.writeInt(count);
return true;
@@ -166,13 +177,14 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case APPLY_BATCH_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
final int numOperations = data.readInt();
final ArrayList<ContentProviderOperation> operations =
new ArrayList<ContentProviderOperation>(numOperations);
for (int i = 0; i < numOperations; i++) {
operations.add(i, ContentProviderOperation.CREATOR.createFromParcel(data));
}
- final ContentProviderResult[] results = applyBatch(operations);
+ final ContentProviderResult[] results = applyBatch(callingPkg, operations);
reply.writeNoException();
reply.writeTypedArray(results, 0);
return true;
@@ -181,11 +193,12 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case DELETE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
String selection = data.readString();
String[] selectionArgs = data.readStringArray();
- int count = delete(url, selection, selectionArgs);
+ int count = delete(callingPkg, url, selection, selectionArgs);
reply.writeNoException();
reply.writeInt(count);
@@ -195,12 +208,13 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case UPDATE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
ContentValues values = ContentValues.CREATOR.createFromParcel(data);
String selection = data.readString();
String[] selectionArgs = data.readStringArray();
- int count = update(url, values, selection, selectionArgs);
+ int count = update(callingPkg, url, values, selection, selectionArgs);
reply.writeNoException();
reply.writeInt(count);
@@ -210,11 +224,12 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case OPEN_FILE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
String mode = data.readString();
ParcelFileDescriptor fd;
- fd = openFile(url, mode);
+ fd = openFile(callingPkg, url, mode);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -229,11 +244,12 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case OPEN_ASSET_FILE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
String mode = data.readString();
AssetFileDescriptor fd;
- fd = openAssetFile(url, mode);
+ fd = openAssetFile(callingPkg, url, mode);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -249,11 +265,12 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
String method = data.readString();
String stringArg = data.readString();
Bundle args = data.readBundle();
- Bundle responseBundle = call(method, stringArg, args);
+ Bundle responseBundle = call(callingPkg, method, stringArg, args);
reply.writeNoException();
reply.writeBundle(responseBundle);
@@ -275,12 +292,13 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case OPEN_TYPED_ASSET_FILE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
String mimeType = data.readString();
Bundle opts = data.readBundle();
AssetFileDescriptor fd;
- fd = openTypedAssetFile(url, mimeType, opts);
+ fd = openTypedAssetFile(callingPkg, url, mimeType, opts);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -329,7 +347,7 @@ final class ContentProviderProxy implements IContentProvider
return mRemote;
}
- public Cursor query(Uri url, String[] projection, String selection,
+ public Cursor query(String callingPkg, Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal)
throws RemoteException {
BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
@@ -338,6 +356,7 @@ final class ContentProviderProxy implements IContentProvider
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
int length = 0;
if (projection != null) {
@@ -405,13 +424,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public Uri insert(Uri url, ContentValues values) throws RemoteException
+ public Uri insert(String callingPkg, Uri url, ContentValues values) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
values.writeToParcel(data, 0);
@@ -426,12 +446,13 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public int bulkInsert(Uri url, ContentValues[] values) throws RemoteException {
+ public int bulkInsert(String callingPkg, Uri url, ContentValues[] values) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
data.writeTypedArray(values, 0);
@@ -446,12 +467,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
- throws RemoteException, OperationApplicationException {
+ public ContentProviderResult[] applyBatch(String callingPkg,
+ ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
data.writeInt(operations.size());
for (ContentProviderOperation operation : operations) {
operation.writeToParcel(data, 0);
@@ -468,13 +491,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public int delete(Uri url, String selection, String[] selectionArgs)
+ public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
data.writeString(selection);
data.writeStringArray(selectionArgs);
@@ -490,13 +514,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public int update(Uri url, ContentValues values, String selection,
+ public int update(String callingPkg, Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
values.writeToParcel(data, 0);
data.writeString(selection);
@@ -513,13 +538,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public ParcelFileDescriptor openFile(Uri url, String mode)
+ public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
data.writeString(mode);
@@ -535,13 +561,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public AssetFileDescriptor openAssetFile(Uri url, String mode)
+ public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
data.writeString(mode);
@@ -558,13 +585,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public Bundle call(String method, String request, Bundle args)
+ public Bundle call(String callingPkg, String method, String request, Bundle args)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
data.writeString(method);
data.writeString(request);
data.writeBundle(args);
@@ -601,13 +629,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
- throws RemoteException, FileNotFoundException {
+ public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
+ Bundle opts) throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
data.writeString(mimeType);
data.writeBundle(opts);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index bde4d2b..fefd343 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -20,6 +20,7 @@ import dalvik.system.CloseGuard;
import android.accounts.Account;
import android.app.ActivityManagerNative;
+import android.app.ActivityThread;
import android.app.AppGlobals;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetFileDescriptor;
@@ -116,6 +117,10 @@ public abstract class ContentResolver {
*/
public static final String SYNC_EXTRAS_INITIALIZE = "initialize";
+ /** @hide */
+ public static final Intent ACTION_SYNC_CONN_STATUS_CHANGED =
+ new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
+
public static final String SCHEME_CONTENT = "content";
public static final String SCHEME_ANDROID_RESOURCE = "android.resource";
public static final String SCHEME_FILE = "file";
@@ -169,6 +174,42 @@ public abstract class ContentResolver {
/** @hide */
public static final int SYNC_ERROR_INTERNAL = 8;
+ private static final String[] SYNC_ERROR_NAMES = new String[] {
+ "already-in-progress",
+ "authentication-error",
+ "io-error",
+ "parse-error",
+ "conflict",
+ "too-many-deletions",
+ "too-many-retries",
+ "internal-error",
+ };
+
+ /** @hide */
+ public static String syncErrorToString(int error) {
+ if (error < 1 || error > SYNC_ERROR_NAMES.length) {
+ return String.valueOf(error);
+ }
+ return SYNC_ERROR_NAMES[error - 1];
+ }
+
+ /** @hide */
+ public static int syncErrorStringToInt(String error) {
+ for (int i = 0, n = SYNC_ERROR_NAMES.length; i < n; i++) {
+ if (SYNC_ERROR_NAMES[i].equals(error)) {
+ return i + 1;
+ }
+ }
+ if (error != null) {
+ try {
+ return Integer.parseInt(error);
+ } catch (NumberFormatException e) {
+ Log.d(TAG, "error parsing sync error: " + error);
+ }
+ }
+ return 0;
+ }
+
public static final int SYNC_OBSERVER_TYPE_SETTINGS = 1<<0;
public static final int SYNC_OBSERVER_TYPE_PENDING = 1<<1;
public static final int SYNC_OBSERVER_TYPE_ACTIVE = 1<<2;
@@ -183,7 +224,8 @@ public abstract class ContentResolver {
private final Random mRandom = new Random(); // guarded by itself
public ContentResolver(Context context) {
- mContext = context;
+ mContext = context != null ? context : ActivityThread.currentApplication();
+ mPackageName = mContext.getBasePackageName();
}
/** @hide */
@@ -358,6 +400,7 @@ public abstract class ContentResolver {
return null;
}
IContentProvider stableProvider = null;
+ Cursor qCursor = null;
try {
long startTime = SystemClock.uptimeMillis();
@@ -367,9 +410,8 @@ public abstract class ContentResolver {
remoteCancellationSignal = unstableProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
- Cursor qCursor;
try {
- qCursor = unstableProvider.query(uri, projection,
+ qCursor = unstableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
} catch (DeadObjectException e) {
// The remote process has died... but we only hold an unstable
@@ -380,26 +422,32 @@ public abstract class ContentResolver {
if (stableProvider == null) {
return null;
}
- qCursor = stableProvider.query(uri, projection,
+ qCursor = stableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
}
if (qCursor == null) {
return null;
}
- // force query execution
+
+ // Force query execution. Might fail and throw a runtime exception here.
qCursor.getCount();
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);
- // Wrap the cursor object into CursorWrapperInner object
+
+ // Wrap the cursor object into CursorWrapperInner object.
CursorWrapperInner wrapper = new CursorWrapperInner(qCursor,
stableProvider != null ? stableProvider : acquireProvider(uri));
stableProvider = null;
+ qCursor = null;
return wrapper;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally {
+ if (qCursor != null) {
+ qCursor.close();
+ }
if (unstableProvider != null) {
releaseUnstableProvider(unstableProvider);
}
@@ -622,7 +670,7 @@ public abstract class ContentResolver {
try {
try {
- fd = unstableProvider.openAssetFile(uri, mode);
+ fd = unstableProvider.openAssetFile(mPackageName, uri, mode);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -636,7 +684,7 @@ public abstract class ContentResolver {
if (stableProvider == null) {
throw new FileNotFoundException("No content provider: " + uri);
}
- fd = stableProvider.openAssetFile(uri, mode);
+ fd = stableProvider.openAssetFile(mPackageName, uri, mode);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -714,7 +762,7 @@ public abstract class ContentResolver {
try {
try {
- fd = unstableProvider.openTypedAssetFile(uri, mimeType, opts);
+ fd = unstableProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -728,7 +776,7 @@ public abstract class ContentResolver {
if (stableProvider == null) {
throw new FileNotFoundException("No content provider: " + uri);
}
- fd = stableProvider.openTypedAssetFile(uri, mimeType, opts);
+ fd = stableProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -863,7 +911,7 @@ public abstract class ContentResolver {
}
try {
long startTime = SystemClock.uptimeMillis();
- Uri createdRow = provider.insert(url, values);
+ Uri createdRow = provider.insert(mPackageName, url, values);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
return createdRow;
@@ -924,7 +972,7 @@ public abstract class ContentResolver {
}
try {
long startTime = SystemClock.uptimeMillis();
- int rowsCreated = provider.bulkInsert(url, values);
+ int rowsCreated = provider.bulkInsert(mPackageName, url, values);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "bulkinsert", null /* where */);
return rowsCreated;
@@ -955,7 +1003,7 @@ public abstract class ContentResolver {
}
try {
long startTime = SystemClock.uptimeMillis();
- int rowsDeleted = provider.delete(url, where, selectionArgs);
+ int rowsDeleted = provider.delete(mPackageName, url, where, selectionArgs);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "delete", where);
return rowsDeleted;
@@ -989,7 +1037,7 @@ public abstract class ContentResolver {
}
try {
long startTime = SystemClock.uptimeMillis();
- int rowsUpdated = provider.update(uri, values, where, selectionArgs);
+ int rowsUpdated = provider.update(mPackageName, uri, values, where, selectionArgs);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, uri, "update", where);
return rowsUpdated;
@@ -1028,7 +1076,7 @@ public abstract class ContentResolver {
throw new IllegalArgumentException("Unknown URI " + uri);
}
try {
- return provider.call(method, arg, extras);
+ return provider.call(mPackageName, method, arg, extras);
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
@@ -1926,7 +1974,13 @@ public abstract class ContentResolver {
return sContentService;
}
+ /** @hide */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
private static IContentService sContentService;
private final Context mContext;
+ final String mPackageName;
private static final String TAG = "ContentResolver";
}
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
deleted file mode 100644
index 4512e82..0000000
--- a/core/java/android/content/ContentService.java
+++ /dev/null
@@ -1,838 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.accounts.Account;
-import android.app.ActivityManager;
-import android.database.IContentObserver;
-import android.database.sqlite.SQLiteException;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-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;
-import java.util.List;
-
-/**
- * {@hide}
- */
-public final class ContentService extends IContentService.Stub {
- private static final String TAG = "ContentService";
- private Context mContext;
- private boolean mFactoryTest;
- private final ObserverNode mRootNode = new ObserverNode("");
- private SyncManager mSyncManager = null;
- private final Object mSyncManagerLock = new Object();
-
- private SyncManager getSyncManager() {
- synchronized(mSyncManagerLock) {
- try {
- // Try to create the SyncManager, return null if it fails (e.g. the disk is full).
- if (mSyncManager == null) mSyncManager = new SyncManager(mContext, mFactoryTest);
- } catch (SQLiteException e) {
- Log.e(TAG, "Can't create SyncManager", e);
- }
- return mSyncManager;
- }
- }
-
- @Override
- protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.DUMP,
- "caller doesn't have the DUMP permission");
-
- // 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();
- try {
- if (mSyncManager == null) {
- pw.println("No SyncManager created! (Disk full?)");
- } else {
- mSyncManager.dump(fd, pw);
- }
- pw.println();
- pw.println("Observer tree:");
- synchronized (mRootNode) {
- int[] counts = new int[2];
- final SparseIntArray pidCounts = new SparseIntArray();
- mRootNode.dumpLocked(fd, pw, args, "", " ", counts, pidCounts);
- pw.println();
- ArrayList<Integer> sorted = new ArrayList<Integer>();
- for (int i=0; i<pidCounts.size(); i++) {
- sorted.add(pidCounts.keyAt(i));
- }
- Collections.sort(sorted, new Comparator<Integer>() {
- @Override
- public int compare(Integer lhs, Integer rhs) {
- int lc = pidCounts.get(lhs);
- int rc = pidCounts.get(rhs);
- if (lc < rc) {
- return 1;
- } else if (lc > rc) {
- return -1;
- }
- return 0;
- }
-
- });
- for (int i=0; i<sorted.size(); i++) {
- int pid = sorted.get(i);
- pw.print(" pid "); pw.print(pid); pw.print(": ");
- pw.print(pidCounts.get(pid)); pw.println(" observers");
- }
- pw.println();
- pw.print(" Total number of nodes: "); pw.println(counts[0]);
- pw.print(" Total number of observers: "); pw.println(counts[1]);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- try {
- return super.onTransact(code, data, reply, flags);
- } catch (RuntimeException e) {
- // The content service only throws security exceptions, so let's
- // log all others.
- if (!(e instanceof SecurityException)) {
- Log.e(TAG, "Content Service Crash", e);
- }
- throw e;
- }
- }
-
- /*package*/ ContentService(Context context, boolean factoryTest) {
- mContext = context;
- mFactoryTest = factoryTest;
- }
-
- public void systemReady() {
- getSyncManager();
- }
-
- /**
- * 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, notifyForDescendants, mRootNode,
- Binder.getCallingUid(), Binder.getCallingPid(), userHandle);
- if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
- " 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");
- }
- synchronized (mRootNode) {
- mRootNode.removeObserverLocked(observer);
- if (false) Log.v(TAG, "Unregistered observer " + observer);
- }
- }
-
- /**
- * 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,
- int userHandle) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- 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);
- }
- }
-
- // 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();
- try {
- ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
- synchronized (mRootNode) {
- mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
- userHandle, calls);
- }
- final int numCalls = calls.size();
- for (int i=0; i<numCalls; i++) {
- ObserverCall oc = calls.get(i);
- try {
- oc.mObserver.onChange(oc.mSelfChange, uri);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
- }
- } catch (RemoteException ex) {
- synchronized (mRootNode) {
- Log.w(TAG, "Found dead observer, removing");
- IBinder binder = oc.mObserver.asBinder();
- final ArrayList<ObserverNode.ObserverEntry> list
- = oc.mNode.mObservers;
- int numList = list.size();
- for (int j=0; j<numList; j++) {
- ObserverNode.ObserverEntry oe = list.get(j);
- if (oe.observer.asBinder() == binder) {
- list.remove(j);
- j--;
- numList--;
- }
- }
- }
- }
- }
- if (syncToNetwork) {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle,
- uri.getAuthority());
- }
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- 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
- * @hide
- *
- */
- public static final class ObserverCall {
- final ObserverNode mNode;
- final IContentObserver mObserver;
- final boolean mSelfChange;
-
- ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange) {
- mNode = node;
- mObserver = observer;
- mSelfChange = selfChange;
- }
- }
-
- public void requestSync(Account account, String authority, Bundle extras) {
- ContentResolver.validateSyncExtrasBundle(extras);
- 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.
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- syncManager.scheduleSync(account, userId, authority, extras, 0 /* no delay */,
- false /* onlyThoseWithUnkownSyncableState */);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- /**
- * Clear all scheduled sync operations that match the uri and cancel the active sync
- * if they match the authority and account, if they are present.
- * @param account filter the pending and active syncs to cancel using this account
- * @param authority filter the pending and active syncs to cancel using this authority
- */
- public void cancelSync(Account account, String authority) {
- 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.
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- syncManager.clearScheduledSyncOperations(account, userId, authority);
- syncManager.cancelActiveSync(account, userId, authority);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- /**
- * Get information about the SyncAdapters that are known to the system.
- * @return an array of SyncAdapters that have registered with the system
- */
- public SyncAdapterType[] getSyncAdapterTypes() {
- // 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.
- final int userId = UserHandle.getCallingUserId();
- final long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- return syncManager.getSyncAdapterTypes(userId);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public boolean getSyncAutomatically(Account account, String providerName) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
- "no permission to read the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().getSyncAutomatically(
- account, userId, providerName);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- return false;
- }
-
- 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 = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- syncManager.getSyncStorageEngine().setSyncAutomatically(
- account, userId, providerName, sync);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void addPeriodicSync(Account account, String authority, Bundle extras,
- long pollFrequency) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
- "no permission to write the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- getSyncManager().getSyncStorageEngine().addPeriodicSync(
- account, userId, authority, extras, pollFrequency);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- 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 = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- getSyncManager().getSyncStorageEngine().removePeriodicSync(account, userId, authority,
- extras);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
- "no permission to read the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
- account, userId, providerName);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public int getIsSyncable(Account account, String providerName) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
- "no permission to read the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().getIsSyncable(
- account, userId, providerName);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- return -1;
- }
-
- 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 = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- syncManager.getSyncStorageEngine().setIsSyncable(
- account, userId, providerName, syncable);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public boolean getMasterSyncAutomatically() {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
- "no permission to read the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(userId);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- return false;
- }
-
- public void setMasterSyncAutomatically(boolean flag) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
- "no permission to write the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public boolean isSyncActive(Account account, String authority) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
- "no permission to read the sync stats");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().isSyncActive(
- account, userId, authority);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- return false;
- }
-
- public List<SyncInfo> getCurrentSyncs() {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
- "no permission to read the sync stats");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public SyncStatusInfo getSyncStatus(Account account, String authority) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
- "no permission to read the sync stats");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority(
- account, userId, authority);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- return null;
- }
-
- public boolean isSyncPending(Account account, String authority) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
- "no permission to read the sync stats");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().isSyncPending(account, userId, authority);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- return false;
- }
-
- public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null && callback != null) {
- syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void removeStatusChangeListener(ISyncStatusObserver callback) {
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null && callback != null) {
- syncManager.getSyncStorageEngine().removeStatusChangeListener(callback);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public static ContentService main(Context context, boolean factoryTest) {
- ContentService service = new ContentService(context, factoryTest);
- ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service);
- return service;
- }
-
- /**
- * Hide this class since it is not part of api,
- * but current unittest framework requires it to be public
- * @hide
- */
- public static final class ObserverNode {
- private class ObserverEntry implements IBinder.DeathRecipient {
- public final IContentObserver observer;
- public final int uid;
- public final int pid;
- 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 _userHandle) {
- this.observersLock = observersLock;
- observer = o;
- uid = _uid;
- pid = _pid;
- userHandle = _userHandle;
- notifyForDescendants = n;
- try {
- observer.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- binderDied();
- }
- }
-
- public void binderDied() {
- synchronized (observersLock) {
- removeObserverLocked(observer);
- }
- }
-
- public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- String name, String prefix, SparseIntArray pidCounts) {
- 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(" user=");
- pw.print(userHandle); pw.print(" target=");
- pw.println(Integer.toHexString(System.identityHashCode(
- observer != null ? observer.asBinder() : null)));
- }
- }
-
- public static final int INSERT_TYPE = 0;
- public static final int UPDATE_TYPE = 1;
- public static final int DELETE_TYPE = 2;
-
- private String mName;
- private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
- private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
-
- public ObserverNode(String name) {
- mName = name;
- }
-
- public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- String name, String prefix, int[] counts, SparseIntArray pidCounts) {
- String innerName = null;
- if (mObservers.size() > 0) {
- if ("".equals(name)) {
- innerName = mName;
- } else {
- innerName = name + "/" + mName;
- }
- for (int i=0; i<mObservers.size(); i++) {
- counts[1]++;
- mObservers.get(i).dumpLocked(fd, pw, args, innerName, prefix,
- pidCounts);
- }
- }
- if (mChildren.size() > 0) {
- if (innerName == null) {
- if ("".equals(name)) {
- innerName = mName;
- } else {
- innerName = name + "/" + mName;
- }
- }
- for (int i=0; i<mChildren.size(); i++) {
- counts[0]++;
- mChildren.get(i).dumpLocked(fd, pw, args, innerName, prefix,
- counts, pidCounts);
- }
- }
- }
-
- private String getUriSegment(Uri uri, int index) {
- if (uri != null) {
- if (index == 0) {
- return uri.getAuthority();
- } else {
- return uri.getPathSegments().get(index - 1);
- }
- } else {
- return null;
- }
- }
-
- private int countUriSegments(Uri uri) {
- if (uri == null) {
- return 0;
- }
- 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 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 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, notifyForDescendants, observersLock,
- uid, pid, userHandle));
- return;
- }
-
- // Look to see if the proper child already exists
- String segment = getUriSegment(uri, index);
- if (segment == null) {
- throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
- }
- int N = mChildren.size();
- for (int i = 0; i < N; i++) {
- ObserverNode node = mChildren.get(i);
- if (node.mName.equals(segment)) {
- node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
- observersLock, uid, pid, userHandle);
- return;
- }
- }
-
- // No child found, create one
- ObserverNode node = new ObserverNode(segment);
- mChildren.add(node);
- node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
- observersLock, uid, pid, userHandle);
- }
-
- public boolean removeObserverLocked(IContentObserver observer) {
- int size = mChildren.size();
- for (int i = 0; i < size; i++) {
- boolean empty = mChildren.get(i).removeObserverLocked(observer);
- if (empty) {
- mChildren.remove(i);
- i--;
- size--;
- }
- }
-
- IBinder observerBinder = observer.asBinder();
- size = mObservers.size();
- for (int i = 0; i < size; i++) {
- ObserverEntry entry = mObservers.get(i);
- if (entry.observer.asBinder() == observerBinder) {
- mObservers.remove(i);
- // We no longer need to listen for death notifications. Remove it.
- observerBinder.unlinkToDeath(entry, 0);
- break;
- }
- }
-
- if (mChildren.size() == 0 && mObservers.size() == 0) {
- return true;
- }
- return false;
- }
-
- private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
- 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 interested
- // in self notifications
- boolean selfChange = (entry.observer.asBinder() == observerBinder);
- if (selfChange && !observerWantsSelfNotifications) {
- continue;
- }
-
- // 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, 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,
- targetUserHandle, calls);
- } else if (index < segmentCount){
- segment = getUriSegment(uri, index);
- // Notify any observers at this level who are interested in descendants
- collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
- targetUserHandle, calls);
- }
-
- int N = mChildren.size();
- for (int i = 0; i < N; i++) {
- ObserverNode node = mChildren.get(i);
- if (segment == null || node.mName.equals(segment)) {
- // We found the child,
- node.collectObserversLocked(uri, index + 1,
- observer, observerWantsSelfNotifications, targetUserHandle, calls);
- if (segment != null) {
- break;
- }
- }
- }
- }
- }
-}
diff --git a/core/java/android/util/Pool.java b/core/java/android/content/ContentValues.aidl
index 8cd4f3e..23d51d8 100644
--- a/core/java/android/util/Pool.java
+++ b/core/java/android/content/ContentValues.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,6 @@
* limitations under the License.
*/
-package android.util;
+package android.content;
-/**
- * @hide
- */
-public interface Pool<T extends Poolable<T>> {
- public abstract T acquire();
- public abstract void release(T element);
-}
+parcelable ContentValues;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7aa2507..5bd28b9 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -45,6 +45,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.List;
/**
* Interface to global information about an application environment. This is
@@ -255,6 +256,12 @@ public abstract class Context {
* Return the Looper for the main thread of the current process. This is
* the thread used to dispatch calls to application components (activities,
* services, etc).
+ * <p>
+ * By definition, this method returns the same result as would be obtained
+ * by calling {@link Looper#getMainLooper() Looper.getMainLooper()}.
+ * </p>
+ *
+ * @return The main looper.
*/
public abstract Looper getMainLooper();
@@ -418,6 +425,9 @@ public abstract class Context {
/** Return the name of this application's package. */
public abstract String getPackageName();
+ /** @hide Return the name of the base context this context is derived from. */
+ public abstract String getBasePackageName();
+
/** Return the full application info for this context's package. */
public abstract ApplicationInfo getApplicationInfo();
@@ -1135,6 +1145,14 @@ public abstract class Context {
String receiverPermission);
/**
+ * Like {@link #sendBroadcast(Intent, String)}, but also allows specification
+ * of an assocated app op as per {@link android.app.AppOpsManager}.
+ * @hide
+ */
+ public abstract void sendBroadcast(Intent intent,
+ String receiverPermission, int appOp);
+
+ /**
* Broadcast the given intent to all interested BroadcastReceivers, delivering
* them one at a time to allow more preferred receivers to consume the
* broadcast before it is delivered to less preferred receivers. This
@@ -1205,6 +1223,17 @@ public abstract class Context {
Bundle initialExtras);
/**
+ * Like {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler,
+ * int, String, android.os.Bundle)}, but also allows specification
+ * of an assocated app op as per {@link android.app.AppOpsManager}.
+ * @hide
+ */
+ public abstract void sendOrderedBroadcast(Intent intent,
+ String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ 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
@@ -1677,7 +1706,7 @@ public abstract class Context {
* argument for use by system server and other multi-user aware code.
* @hide
*/
- public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) {
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
@@ -1993,17 +2022,6 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a {@link
- * android.net.ThrottleManager} for handling management of
- * throttling.
- *
- * @hide
- * @see #getSystemService
- * @see android.net.ThrottleManager
- */
- public static final String THROTTLE_SERVICE = "throttle";
-
- /**
- * Use with {@link #getSystemService} to retrieve a {@link
* android.os.IUpdateLock} for managing runtime sequences that
* must not be interrupted by headless OTA application or similar.
*
@@ -2182,7 +2200,6 @@ public abstract class Context {
* {@link android.bluetooth.BluetoothAdapter} for using Bluetooth.
*
* @see #getSystemService
- * @hide
*/
public static final String BLUETOOTH_SERVICE = "bluetooth";
@@ -2255,6 +2272,18 @@ public abstract class Context {
public static final String USER_SERVICE = "user";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.AppOpsManager} for tracking application operations
+ * on the device.
+ *
+ * @see #getSystemService
+ * @see android.app.AppOpsManager
+ *
+ * @hide
+ */
+ public static final String APP_OPS_SERVICE = "appops";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
@@ -2668,6 +2697,14 @@ public abstract class Context {
throws PackageManager.NameNotFoundException;
/**
+ * Get the userId associated with this context
+ * @return user id
+ *
+ * @hide
+ */
+ public abstract int getUserId();
+
+ /**
* 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
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 84ad667..2f1bf8c 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -135,6 +135,12 @@ public class ContextWrapper extends Context {
return mBase.getPackageName();
}
+ /** @hide */
+ @Override
+ public String getBasePackageName() {
+ return mBase.getBasePackageName();
+ }
+
@Override
public ApplicationInfo getApplicationInfo() {
return mBase.getApplicationInfo();
@@ -343,6 +349,12 @@ public class ContextWrapper extends Context {
mBase.sendBroadcast(intent, receiverPermission);
}
+ /** @hide */
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
+ mBase.sendBroadcast(intent, receiverPermission, appOp);
+ }
+
@Override
public void sendOrderedBroadcast(Intent intent,
String receiverPermission) {
@@ -359,6 +371,17 @@ public class ContextWrapper extends Context {
initialData, initialExtras);
}
+ /** @hide */
+ @Override
+ public void sendOrderedBroadcast(
+ Intent intent, String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission, appOp,
+ resultReceiver, scheduler, initialCode,
+ initialData, initialExtras);
+ }
+
@Override
public void sendBroadcastAsUser(Intent intent, UserHandle user) {
mBase.sendBroadcastAsUser(intent, user);
@@ -475,8 +498,9 @@ public class ContextWrapper extends Context {
/** @hide */
@Override
- public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) {
- return mBase.bindService(service, conn, flags, userHandle);
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+ UserHandle user) {
+ return mBase.bindServiceAsUser(service, conn, flags, user);
}
@Override
@@ -599,6 +623,12 @@ public class ContextWrapper extends Context {
return mBase.createPackageContextAsUser(packageName, flags, user);
}
+ /** @hide */
+ @Override
+ public int getUserId() {
+ return mBase.getUserId();
+ }
+
@Override
public Context createConfigurationContext(Configuration overrideConfiguration) {
return mBase.createConfigurationContext(overrideConfiguration);
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 9f7a104..5d7d677 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -65,9 +65,14 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
mSelectionArgs, mSortOrder, mCancellationSignal);
if (cursor != null) {
- // Ensure the cursor window is filled
- cursor.getCount();
- registerContentObserver(cursor, mObserver);
+ try {
+ // Ensure the cursor window is filled.
+ cursor.getCount();
+ cursor.registerContentObserver(mObserver);
+ } catch (RuntimeException ex) {
+ cursor.close();
+ throw ex;
+ }
}
return cursor;
} finally {
@@ -88,14 +93,6 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
}
}
- /**
- * Registers an observer to get notifications from the content provider
- * when the cursor needs to be refreshed.
- */
- void registerContentObserver(Cursor cursor, ContentObserver observer) {
- cursor.registerContentObserver(mObserver);
- }
-
/* Runs on the UI thread */
@Override
public void deliverResult(Cursor cursor) {
diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl
index 254901b..af0b8f0 100644
--- a/core/java/android/content/IClipboard.aidl
+++ b/core/java/android/content/IClipboard.aidl
@@ -26,15 +26,16 @@ import android.content.IOnPrimaryClipChangedListener;
* {@hide}
*/
interface IClipboard {
- void setPrimaryClip(in ClipData clip);
+ void setPrimaryClip(in ClipData clip, String callingPackage);
ClipData getPrimaryClip(String pkg);
- ClipDescription getPrimaryClipDescription();
- boolean hasPrimaryClip();
- void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener);
+ ClipDescription getPrimaryClipDescription(String callingPackage);
+ boolean hasPrimaryClip(String callingPackage);
+ void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener,
+ String callingPackage);
void removePrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener);
/**
* Returns true if the clipboard contains text; false otherwise.
*/
- boolean hasClipboardText();
+ boolean hasClipboardText(String callingPackage);
}
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index eeba1e0..62b79f0 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -34,30 +34,33 @@ import java.util.ArrayList;
* @hide
*/
public interface IContentProvider extends IInterface {
- public Cursor query(Uri url, String[] projection, String selection,
+ public Cursor query(String callingPkg, Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal)
throws RemoteException;
public String getType(Uri url) throws RemoteException;
- public Uri insert(Uri url, ContentValues initialValues)
+ public Uri insert(String callingPkg, Uri url, ContentValues initialValues)
throws RemoteException;
- public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException;
- public int delete(Uri url, String selection, String[] selectionArgs)
+ public int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues)
throws RemoteException;
- public int update(Uri url, ContentValues values, String selection,
+ public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
+ throws RemoteException;
+ public int update(String callingPkg, Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException;
- public ParcelFileDescriptor openFile(Uri url, String mode)
+ public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode)
throws RemoteException, FileNotFoundException;
- public AssetFileDescriptor openAssetFile(Uri url, String mode)
+ public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode)
throws RemoteException, FileNotFoundException;
- public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
- throws RemoteException, OperationApplicationException;
- public Bundle call(String method, String arg, Bundle extras) throws RemoteException;
+ public ContentProviderResult[] applyBatch(String callingPkg,
+ ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException;
+ public Bundle call(String callingPkg, String method, String arg, Bundle extras)
+ throws RemoteException;
public ICancellationSignal createCancellationSignal() throws RemoteException;
// Data interchange.
public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
- public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
- throws RemoteException, FileNotFoundException;
+ public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
+ Bundle opts) throws RemoteException, FileNotFoundException;
/* IPC constants */
static final String descriptor = "android.content.IContentProvider";
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b7499c5..fc95728 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -32,6 +32,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.StrictMode;
import android.util.AttributeSet;
import android.util.Log;
@@ -883,7 +884,7 @@ public class Intent implements Parcelable, Cloneable {
* Activity Action: Allow the user to select a particular kind of data and
* return it. This is different than {@link #ACTION_PICK} in that here we
* just say what kind of data is desired, not a URI of existing data from
- * which the user can pick. A ACTION_GET_CONTENT could allow the user to
+ * which the user can pick. An ACTION_GET_CONTENT could allow the user to
* create the data as it runs (for example taking a picture or recording a
* sound), let them browse over the web and download the desired data,
* etc.
@@ -917,12 +918,17 @@ public class Intent implements Parcelable, Cloneable {
* from a remote server but not already on the local device (thus requiring
* they be downloaded when opened).
* <p>
+ * If the caller can handle multiple returned items (the user performing
+ * multiple selection), then it can specify {@link #EXTRA_ALLOW_MULTIPLE}
+ * to indicate this.
+ * <p>
* Input: {@link #getType} is the desired MIME type to retrieve. Note
* that no URI is supplied in the intent, as there are no constraints on
* where the returned data originally comes from. You may also include the
* {@link #CATEGORY_OPENABLE} if you can only accept data that can be
* opened as a stream. You may use {@link #EXTRA_LOCAL_ONLY} to limit content
- * selection to local data.
+ * selection to local data. You may use {@link #EXTRA_ALLOW_MULTIPLE} to
+ * allow the user to select multiple items.
* <p>
* Output: The URI of the item that was picked. This must be a content:
* URI so that any receiver can access it.
@@ -1140,14 +1146,47 @@ public class Intent implements Parcelable, Cloneable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
+
/**
* Activity Action: Perform assist action.
* <p>
- * Input: nothing
+ * Input: {@link #EXTRA_ASSIST_PACKAGE} and {@link #EXTRA_ASSIST_CONTEXT} can provide
+ * additional optional contextual information about where the user was when they requested
+ * the assist.
* Output: nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_ASSIST = "android.intent.action.ASSIST";
+
+ /**
+ * Activity Action: Perform voice assist action.
+ * <p>
+ * Input: {@link #EXTRA_ASSIST_PACKAGE} and {@link #EXTRA_ASSIST_CONTEXT} can provide
+ * additional optional contextual information about where the user was when they requested
+ * the voice assist.
+ * Output: nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST";
+
+ /**
+ * An optional field on {@link #ACTION_ASSIST}
+ * containing the name of the current foreground application package at the time
+ * the assist was invoked.
+ */
+ public static final String EXTRA_ASSIST_PACKAGE
+ = "android.intent.extra.ASSIST_PACKAGE";
+
+ /**
+ * An optional field on {@link #ACTION_ASSIST}
+ * containing additional contextual information supplied by the current
+ * foreground app at the time of the assist request. This is a {@link Bundle} of
+ * additional data.
+ */
+ public static final String EXTRA_ASSIST_CONTEXT
+ = "android.intent.extra.ASSIST_CONTEXT";
+
/**
* Activity Action: List all available applications
* <p>Input: Nothing.
@@ -1588,7 +1627,7 @@ public class Intent implements Parcelable, Cloneable {
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
* <li> {@link #EXTRA_CHANGED_COMPONENT_NAME_LIST} containing the class name
- * of the changed components.
+ * of the changed components (or the package name itself).
* <li> {@link #EXTRA_DONT_KILL_APP} containing boolean field to override the
* default action of restarting the application.
* </ul>
@@ -2299,6 +2338,65 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.DOCK_EVENT";
/**
+ * Broadcast Action: A broadcast when idle maintenance can be started.
+ * This means that the user is not interacting with the device and is
+ * not expected to do so soon. Typical use of the idle maintenance is
+ * to perform somehow expensive tasks that can be postponed at a moment
+ * when they will not degrade user experience.
+ * <p>
+ * <p class="note">In order to keep the device responsive in case of an
+ * unexpected user interaction, implementations of a maintenance task
+ * should be interruptible. In such a scenario a broadcast with action
+ * {@link #ACTION_IDLE_MAINTENANCE_END} will be sent. In other words, you
+ * should not do the maintenance work in
+ * {@link BroadcastReceiver#onReceive(Context, Intent)}, rather start a
+ * maintenance service by {@link Context#startService(Intent)}. Also
+ * you should hold a wake lock while your maintenance service is running
+ * to prevent the device going to sleep.
+ * </p>
+ * <p>
+ * <p class="note">This is a protected intent that can only be sent by
+ * the system.
+ * </p>
+ *
+ * @see #ACTION_IDLE_MAINTENANCE_END
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_IDLE_MAINTENANCE_START =
+ "android.intent.action.ACTION_IDLE_MAINTENANCE_START";
+
+ /**
+ * Broadcast Action: A broadcast when idle maintenance should be stopped.
+ * This means that the user was not interacting with the device as a result
+ * of which a broadcast with action {@link #ACTION_IDLE_MAINTENANCE_START}
+ * was sent and now the user started interacting with the device. Typical
+ * use of the idle maintenance is to perform somehow expensive tasks that
+ * can be postponed at a moment when they will not degrade user experience.
+ * <p>
+ * <p class="note">In order to keep the device responsive in case of an
+ * unexpected user interaction, implementations of a maintenance task
+ * should be interruptible. Hence, on receiving a broadcast with this
+ * action, the maintenance task should be interrupted as soon as possible.
+ * In other words, you should not do the maintenance work in
+ * {@link BroadcastReceiver#onReceive(Context, Intent)}, rather stop the
+ * maintenance service that was started on receiving of
+ * {@link #ACTION_IDLE_MAINTENANCE_START}.Also you should release the wake
+ * lock you acquired when your maintenance service started.
+ * </p>
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @see #ACTION_IDLE_MAINTENANCE_START
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_IDLE_MAINTENANCE_END =
+ "android.intent.action.ACTION_IDLE_MAINTENANCE_END";
+
+ /**
* Broadcast Action: a remote intent is to be broadcasted.
*
* A remote intent is used for remote RPC between devices. The remote intent
@@ -2326,6 +2424,23 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.PRE_BOOT_COMPLETED";
/**
+ * Broadcast to a specific application to query any supported restrictions to impose
+ * on restricted users. The broadcast intent contains an extra
+ * {@link #EXTRA_RESTRICTIONS_BUNDLE} with the currently persisted
+ * restrictions as a Bundle of key/value pairs. The value types can be Boolean, String or
+ * String[] depending on the restriction type.<p/>
+ * The response should contain an extra {@link #EXTRA_RESTRICTIONS_LIST},
+ * which is of type <code>ArrayList&lt;RestrictionEntry&gt;</code>. It can also
+ * contain an extra {@link #EXTRA_RESTRICTIONS_INTENT}, which is of type <code>Intent</code>.
+ * The activity specified by that intent will be launched for a result which must contain
+ * one of the extras {@link #EXTRA_RESTRICTIONS_LIST} or {@link #EXTRA_RESTRICTIONS_BUNDLE}.
+ * The keys and values of the returned restrictions will be persisted.
+ * @see RestrictionEntry
+ */
+ public static final String ACTION_GET_RESTRICTION_ENTRIES =
+ "android.intent.action.GET_RESTRICTION_ENTRIES";
+
+ /**
* 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
@@ -2456,8 +2571,7 @@ public class Intent implements Parcelable, Cloneable {
/**
* 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.
+ * This is only sent to registered receivers, not manifest receivers. It is sent to all users.
* @hide
*/
public static final String ACTION_USER_INFO_CHANGED =
@@ -2469,6 +2583,22 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_QUICK_CLOCK =
"android.intent.action.QUICK_CLOCK";
+ /**
+ * Broadcast Action: This is broadcast when a user action should request the
+ * brightness setting dialog.
+ * @hide
+ */
+ public static final String ACTION_SHOW_BRIGHTNESS_DIALOG =
+ "android.intent.action.SHOW_BRIGHTNESS_DIALOG";
+
+ /**
+ * Broadcast Action: A global button was pressed. Includes a single
+ * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
+ * caused the broadcast.
+ * @hide
+ */
+ public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -2590,7 +2720,8 @@ public class Intent implements Parcelable, Cloneable {
public static final String CATEGORY_SAMPLE_CODE = "android.intent.category.SAMPLE_CODE";
/**
* Used to indicate that a GET_CONTENT intent only wants URIs that can be opened with
- * ContentResolver.openInputStream. Openable URIs must support the columns in OpenableColumns
+ * ContentResolver.openInputStream. Openable URIs must support the columns in
+ * {@link android.provider.OpenableColumns}
* when queried, though it is allowable for those columns to be blank.
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
@@ -2973,7 +3104,9 @@ public class Intent implements Parcelable, Cloneable {
/**
* This field is part of {@link android.content.Intent#ACTION_PACKAGE_CHANGED},
- * and contains a string array of all of the components that have changed.
+ * and contains a string array of all of the components that have changed. If
+ * the state of the overall package has changed, then it will contain an entry
+ * with the package name itself.
*/
public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST =
"android.intent.extra.changed_component_name_list";
@@ -3028,13 +3161,47 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.extra.LOCAL_ONLY";
/**
- * The userHandle carried with broadcast intents related to addition, removal and switching of users
+ * Used to indicate that a {@link #ACTION_GET_CONTENT} intent can allow the
+ * user to select and return multiple items. This is a boolean extra; the default
+ * is false. If true, an implementation of ACTION_GET_CONTENT is allowed to
+ * present the user with a UI where they can pick multiple items that are all
+ * returned to the caller. When this happens, they should be returned as
+ * the {@link #getClipData()} part of the result Intent.
+ */
+ public static final String EXTRA_ALLOW_MULTIPLE =
+ "android.intent.extra.ALLOW_MULTIPLE";
+
+ /**
+ * 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_USER_HANDLE =
"android.intent.extra.user_handle";
+ /**
+ * Extra used in the response from a BroadcastReceiver that handles
+ * {@link #ACTION_GET_RESTRICTION_ENTRIES}. The type of the extra is
+ * <code>ArrayList&lt;RestrictionEntry&gt;</code>.
+ */
+ public static final String EXTRA_RESTRICTIONS_LIST = "android.intent.extra.restrictions_list";
+
+ /**
+ * Extra sent in the intent to the BroadcastReceiver that handles
+ * {@link #ACTION_GET_RESTRICTION_ENTRIES}. The type of the extra is a Bundle containing
+ * the restrictions as key/value pairs.
+ */
+ public static final String EXTRA_RESTRICTIONS_BUNDLE =
+ "android.intent.extra.restrictions_bundle";
+
+ /**
+ * Extra used in the response from a BroadcastReceiver that handles
+ * {@link #ACTION_GET_RESTRICTION_ENTRIES}.
+ */
+ public static final String EXTRA_RESTRICTIONS_INTENT =
+ "android.intent.extra.restrictions_intent";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
@@ -6822,6 +6989,32 @@ public class Intent implements Parcelable, Cloneable {
}
/**
+ * Prepare this {@link Intent} to leave an app process.
+ *
+ * @hide
+ */
+ public void prepareToLeaveProcess() {
+ setAllowFds(false);
+
+ if (mSelector != null) {
+ mSelector.prepareToLeaveProcess();
+ }
+ if (mClipData != null) {
+ mClipData.prepareToLeaveProcess();
+ }
+
+ if (mData != null && StrictMode.vmFileUriExposureEnabled()) {
+ // There are several ACTION_MEDIA_* broadcasts that send file://
+ // Uris, so only check common actions.
+ if (ACTION_VIEW.equals(mAction) ||
+ ACTION_EDIT.equals(mAction) ||
+ ACTION_ATTACH_DATA.equals(mAction)) {
+ mData.checkFileUriExposed("Intent.getData()");
+ }
+ }
+ }
+
+ /**
* Migrate any {@link #EXTRA_STREAM} in {@link #ACTION_SEND} and
* {@link #ACTION_SEND_MULTIPLE} to {@link ClipData}. Also inspects nested
* intents in {@link #ACTION_CHOOSER}.
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 3b0d846..5e65b59 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -86,7 +86,8 @@ import java.util.Set;
* <strong>data scheme+authority+path</strong> if specified) must match.
*
* <p><strong>Action</strong> matches if any of the given values match the
- * Intent action, <em>or</em> if no actions were specified in the filter.
+ * Intent action; if the filter specifies no actions, then it will only match
+ * Intents that do not contain an action.
*
* <p><strong>Data Type</strong> matches if any of the given values match the
* Intent type. The Intent
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index 3052414..911e49c 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -58,6 +58,7 @@ public class Loader<D> {
boolean mAbandoned = false;
boolean mReset = true;
boolean mContentChanged = false;
+ boolean mProcessingChange = false;
/**
* An implementation of a ContentObserver that takes care of connecting
@@ -439,6 +440,7 @@ public class Loader<D> {
mStarted = false;
mAbandoned = false;
mContentChanged = false;
+ mProcessingChange = false;
}
/**
@@ -458,9 +460,34 @@ public class Loader<D> {
public boolean takeContentChanged() {
boolean res = mContentChanged;
mContentChanged = false;
+ mProcessingChange |= res;
return res;
}
-
+
+ /**
+ * Commit that you have actually fully processed a content change that
+ * was returned by {@link #takeContentChanged}. This is for use with
+ * {@link #rollbackContentChanged()} to handle situations where a load
+ * is cancelled. Call this when you have completely processed a load
+ * without it being cancelled.
+ */
+ public void commitContentChanged() {
+ mProcessingChange = false;
+ }
+
+ /**
+ * Report that you have abandoned the processing of a content change that
+ * was returned by {@link #takeContentChanged()} and would like to rollback
+ * to the state where there is again a pending content change. This is
+ * to handle the case where a data load due to a content change has been
+ * canceled before its data was delivered back to the loader.
+ */
+ public void rollbackContentChanged() {
+ if (mProcessingChange) {
+ mContentChanged = true;
+ }
+ }
+
/**
* Called when {@link ForceLoadContentObserver} detects a change. The
* default implementation checks to see if the loader is currently started;
@@ -512,9 +539,14 @@ public class Loader<D> {
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
writer.print(prefix); writer.print("mId="); writer.print(mId);
writer.print(" mListener="); writer.println(mListener);
- writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
- writer.print(" mContentChanged="); writer.print(mContentChanged);
- writer.print(" mAbandoned="); writer.print(mAbandoned);
- writer.print(" mReset="); writer.println(mReset);
+ if (mStarted || mContentChanged || mProcessingChange) {
+ writer.print(prefix); writer.print("mStarted="); writer.print(mStarted);
+ writer.print(" mContentChanged="); writer.print(mContentChanged);
+ writer.print(" mProcessingChange="); writer.println(mProcessingChange);
+ }
+ if (mAbandoned || mReset) {
+ writer.print(prefix); writer.print("mAbandoned="); writer.print(mAbandoned);
+ writer.print(" mReset="); writer.println(mReset);
+ }
}
} \ No newline at end of file
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index 17813ec..513a556 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -79,6 +79,25 @@ public class PeriodicSync implements Parcelable {
return account.equals(other.account)
&& authority.equals(other.authority)
&& period == other.period
- && SyncStorageEngine.equals(extras, other.extras);
+ && syncExtrasEquals(extras, other.extras);
+ }
+
+ /** {@hide} */
+ public static boolean syncExtrasEquals(Bundle b1, Bundle b2) {
+ if (b1.size() != b2.size()) {
+ return false;
+ }
+ if (b1.isEmpty()) {
+ return true;
+ }
+ for (String key : b1.keySet()) {
+ if (!b2.containsKey(key)) {
+ return false;
+ }
+ if (!b1.get(key).equals(b2.get(key))) {
+ return false;
+ }
+ }
+ return true;
}
}
diff --git a/core/java/android/content/RestrictionEntry.aidl b/core/java/android/content/RestrictionEntry.aidl
new file mode 100644
index 0000000..b93eee3
--- /dev/null
+++ b/core/java/android/content/RestrictionEntry.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.content;
+
+parcelable RestrictionEntry;
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
new file mode 100644
index 0000000..283a097
--- /dev/null
+++ b/core/java/android/content/RestrictionEntry.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Inherited;
+
+/**
+ * Applications can expose restrictions for a restricted user on a
+ * multiuser device. The administrator can configure these restrictions that will then be
+ * applied to the restricted user. Each RestrictionsEntry is one configurable restriction.
+ * <p/>
+ * Any application that chooses to expose such restrictions does so by implementing a
+ * receiver that handles the {@link Intent#ACTION_GET_RESTRICTION_ENTRIES} action.
+ * The receiver then returns a result bundle that contains an entry called "restrictions", whose
+ * value is an ArrayList<RestrictionsEntry>.
+ */
+public class RestrictionEntry implements Parcelable {
+
+ /**
+ * A type of restriction. Use this type for information that needs to be transferred across
+ * but shouldn't be presented to the user in the UI. Stores a single String value.
+ */
+ public static final int TYPE_NULL = 0;
+
+ /**
+ * A type of restriction. Use this for storing a boolean value, typically presented as
+ * a checkbox in the UI.
+ */
+ public static final int TYPE_BOOLEAN = 1;
+
+ /**
+ * A type of restriction. Use this for storing a string value, typically presented as
+ * a single-select list. Call {@link #setChoiceEntries(String[])} and
+ * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
+ * and the corresponding values, respectively.
+ */
+ public static final int TYPE_CHOICE = 2;
+
+ /**
+ * A type of restriction. Use this for storing a string value, typically presented as
+ * a single-select list. Call {@link #setChoiceEntries(String[])} and
+ * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
+ * and the corresponding values, respectively.
+ * The presentation could imply that values in lower array indices are included when a
+ * particular value is chosen.
+ * @hide
+ */
+ public static final int TYPE_CHOICE_LEVEL = 3;
+
+ /**
+ * A type of restriction. Use this for presenting a multi-select list where more than one
+ * entry can be selected, such as for choosing specific titles to white-list.
+ * Call {@link #setChoiceEntries(String[])} and
+ * {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
+ * and the corresponding values, respectively.
+ * Use {@link #getAllSelectedStrings()} and {@link #setAllSelectedStrings(String[])} to
+ * manipulate the selections.
+ */
+ public static final int TYPE_MULTI_SELECT = 4;
+
+ /** The type of restriction. */
+ private int type;
+
+ /** The unique key that identifies the restriction. */
+ private String key;
+
+ /** The user-visible title of the restriction. */
+ private String title;
+
+ /** The user-visible secondary description of the restriction. */
+ private String description;
+
+ /** The user-visible set of choices used for single-select and multi-select lists. */
+ private String [] choices;
+
+ /** The values corresponding to the user-visible choices. The value(s) of this entry will
+ * one or more of these, returned by {@link #getAllSelectedStrings()} and
+ * {@link #getSelectedString()}.
+ */
+ private String [] values;
+
+ /* The chosen value, whose content depends on the type of the restriction. */
+ private String currentValue;
+
+ /* List of selected choices in the multi-select case. */
+ private String[] currentValues;
+
+ /**
+ * Constructor for {@link #TYPE_CHOICE} type.
+ * @param key the unique key for this restriction
+ * @param selectedString the current value
+ */
+ public RestrictionEntry(String key, String selectedString) {
+ this.key = key;
+ this.type = TYPE_CHOICE;
+ this.currentValue = selectedString;
+ }
+
+ /**
+ * Constructor for {@link #TYPE_BOOLEAN} type.
+ * @param key the unique key for this restriction
+ * @param selectedState whether this restriction is selected or not
+ */
+ public RestrictionEntry(String key, boolean selectedState) {
+ this.key = key;
+ this.type = TYPE_BOOLEAN;
+ setSelectedState(selectedState);
+ }
+
+ /**
+ * Constructor for {@link #TYPE_MULTI_SELECT} type.
+ * @param key the unique key for this restriction
+ * @param selectedStrings the list of values that are currently selected
+ */
+ public RestrictionEntry(String key, String[] selectedStrings) {
+ this.key = key;
+ this.type = TYPE_MULTI_SELECT;
+ this.currentValues = selectedStrings;
+ }
+
+ /**
+ * Sets the type for this restriction.
+ * @param type the type for this restriction.
+ */
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns the type for this restriction.
+ * @return the type for this restriction
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Returns the currently selected string value.
+ * @return the currently selected value, which can be null for types that aren't for holding
+ * single string values.
+ */
+ public String getSelectedString() {
+ return currentValue;
+ }
+
+ /**
+ * Returns the list of currently selected values.
+ * @return the list of current selections, if type is {@link #TYPE_MULTI_SELECT},
+ * null otherwise.
+ */
+ public String[] getAllSelectedStrings() {
+ return currentValues;
+ }
+
+ /**
+ * Returns the current selected state for an entry of type {@link #TYPE_BOOLEAN}.
+ * @return the current selected state of the entry.
+ */
+ public boolean getSelectedState() {
+ return Boolean.parseBoolean(currentValue);
+ }
+
+ /**
+ * Sets the string value to use as the selected value for this restriction. This value will
+ * be persisted by the system for later use by the application.
+ * @param selectedString the string value to select.
+ */
+ public void setSelectedString(String selectedString) {
+ currentValue = selectedString;
+ }
+
+ /**
+ * Sets the current selected state for an entry of type {@link #TYPE_BOOLEAN}. This value will
+ * be persisted by the system for later use by the application.
+ * @param state the current selected state
+ */
+ public void setSelectedState(boolean state) {
+ currentValue = Boolean.toString(state);
+ }
+
+ /**
+ * Sets the current list of selected values for an entry of type {@link #TYPE_MULTI_SELECT}.
+ * These values will be persisted by the system for later use by the application.
+ * @param allSelectedStrings the current list of selected values.
+ */
+ public void setAllSelectedStrings(String[] allSelectedStrings) {
+ currentValues = allSelectedStrings;
+ }
+
+ /**
+ * Sets a list of string values that can be selected by the user. If no user-visible entries
+ * are set by a call to {@link #setChoiceEntries(String[])}, these values will be the ones
+ * shown to the user. Values will be chosen from this list as the user's selection and the
+ * selected values can be retrieved by a call to {@link #getAllSelectedStrings()}, or
+ * {@link #getSelectedString()}, depending on whether it is a multi-select type or choice type.
+ * This method is not relevant for types other than
+ * {@link #TYPE_CHOICE}, and {@link #TYPE_MULTI_SELECT}.
+ * @param choiceValues an array of Strings which will be the selected values for the user's
+ * selections.
+ * @see #getChoiceValues()
+ * @see #getAllSelectedStrings()
+ */
+ public void setChoiceValues(String[] choiceValues) {
+ values = choiceValues;
+ }
+
+ /**
+ * Sets a list of string values that can be selected by the user, similar to
+ * {@link #setChoiceValues(String[])}.
+ * @param context the application context for retrieving the resources.
+ * @param stringArrayResId the resource id for a string array containing the possible values.
+ * @see #setChoiceValues(String[])
+ */
+ public void setChoiceValues(Context context, int stringArrayResId) {
+ values = context.getResources().getStringArray(stringArrayResId);
+ }
+
+ /**
+ * Returns the list of possible string values set earlier.
+ * @return the list of possible values.
+ */
+ public String[] getChoiceValues() {
+ return values;
+ }
+
+ /**
+ * Sets a list of strings that will be presented as choices to the user. When the
+ * user selects one or more of these choices, the corresponding value from the possible values
+ * are stored as the selected strings. The size of this array must match the size of the array
+ * set in {@link #setChoiceValues(String[])}. This method is not relevant for types other
+ * than {@link #TYPE_CHOICE}, and {@link #TYPE_MULTI_SELECT}.
+ * @param choiceEntries the list of user-visible choices.
+ * @see #setChoiceValues(String[])
+ */
+ public void setChoiceEntries(String[] choiceEntries) {
+ choices = choiceEntries;
+ }
+
+ /** Sets a list of strings that will be presented as choices to the user. This is similar to
+ * {@link #setChoiceEntries(String[])}.
+ * @param context the application context, used for retrieving the resources.
+ * @param stringArrayResId the resource id of a string array containing the possible entries.
+ */
+ public void setChoiceEntries(Context context, int stringArrayResId) {
+ choices = context.getResources().getStringArray(stringArrayResId);
+ }
+
+ /**
+ * Returns the list of strings, set earlier, that will be presented as choices to the user.
+ * @return the list of choices presented to the user.
+ */
+ public String[] getChoiceEntries() {
+ return choices;
+ }
+
+ /**
+ * Returns the provided user-visible description of the entry, if any.
+ * @return the user-visible description, null if none was set earlier.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Sets the user-visible description of the entry, as a possible sub-text for the title.
+ * You can use this to describe the entry in more detail or to display the current state of
+ * the restriction.
+ * @param description the user-visible description string.
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /**
+ * This is the unique key for the restriction entry.
+ * @return the key for the restriction.
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Returns the user-visible title for the entry, if any.
+ * @return the user-visible title for the entry, null if none was set earlier.
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Sets the user-visible title for the entry.
+ * @param title the user-visible title for the entry.
+ */
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ private boolean equalArrays(String[] one, String[] other) {
+ if (one.length != other.length) return false;
+ for (int i = 0; i < one.length; i++) {
+ if (!one[i].equals(other[i])) return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof RestrictionEntry)) return false;
+ final RestrictionEntry other = (RestrictionEntry) o;
+ // Make sure that either currentValue matches or currentValues matches.
+ return type == other.type && key.equals(other.key)
+ &&
+ ((currentValues == null && other.currentValues == null
+ && currentValue != null && currentValue.equals(other.currentValue))
+ ||
+ (currentValue == null && other.currentValue == null
+ && currentValues != null && equalArrays(currentValues, other.currentValues)));
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + key.hashCode();
+ if (currentValue != null) {
+ result = 31 * result + currentValue.hashCode();
+ } else if (currentValues != null) {
+ for (String value : currentValues) {
+ if (value != null) {
+ result = 31 * result + value.hashCode();
+ }
+ }
+ }
+ return result;
+ }
+
+ private String[] readArray(Parcel in) {
+ int count = in.readInt();
+ String[] values = new String[count];
+ for (int i = 0; i < count; i++) {
+ values[i] = in.readString();
+ }
+ return values;
+ }
+
+ public RestrictionEntry(Parcel in) {
+ type = in.readInt();
+ key = in.readString();
+ title = in.readString();
+ description = in.readString();
+ choices = readArray(in);
+ values = readArray(in);
+ currentValue = in.readString();
+ currentValues = readArray(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private void writeArray(Parcel dest, String[] values) {
+ if (values == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(values.length);
+ for (int i = 0; i < values.length; i++) {
+ dest.writeString(values[i]);
+ }
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(type);
+ dest.writeString(key);
+ dest.writeString(title);
+ dest.writeString(description);
+ writeArray(dest, choices);
+ writeArray(dest, values);
+ dest.writeString(currentValue);
+ writeArray(dest, currentValues);
+ }
+
+ public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() {
+ public RestrictionEntry createFromParcel(Parcel source) {
+ return new RestrictionEntry(source);
+ }
+
+ public RestrictionEntry[] newArray(int size) {
+ return new RestrictionEntry[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}";
+ }
+}
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index da5480e..d4f7f06 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -71,7 +71,9 @@ public interface SharedPreferences {
* {@link #commit} or {@link #apply} are called.
*
* @param key The name of the preference to modify.
- * @param value The new value for the preference.
+ * @param value The new value for the preference. Supplying {@code null}
+ * as the value is equivalent to calling {@link #remove(String)} with
+ * this key.
*
* @return Returns a reference to the same Editor object, so you can
* chain put calls together.
@@ -83,7 +85,9 @@ public interface SharedPreferences {
* back once {@link #commit} is called.
*
* @param key The name of the preference to modify.
- * @param values The new values for the preference.
+ * @param values The set of new values for the preference. Passing {@code null}
+ * for this argument is equivalent to calling {@link #remove(String)} with
+ * this key.
* @return Returns a reference to the same Editor object, so you can
* chain put calls together.
*/
diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java
index 7b643a0..8bb3ee7 100644
--- a/core/java/android/content/SyncAdaptersCache.java
+++ b/core/java/android/content/SyncAdaptersCache.java
@@ -31,7 +31,7 @@ import java.io.IOException;
* A cache of services that export the {@link android.content.ISyncAdapter} interface.
* @hide
*/
-/* package private */ class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType> {
+public class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType> {
private static final String TAG = "Account";
private static final String SERVICE_INTERFACE = "android.content.SyncAdapter";
@@ -39,7 +39,7 @@ import java.io.IOException;
private static final String ATTRIBUTES_NAME = "sync-adapter";
private static final MySerializer sSerializer = new MySerializer();
- SyncAdaptersCache(Context context) {
+ public SyncAdaptersCache(Context context) {
super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, sSerializer);
}
diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java
index abfe964..0284882 100644
--- a/core/java/android/content/SyncInfo.java
+++ b/core/java/android/content/SyncInfo.java
@@ -46,7 +46,7 @@ public class SyncInfo implements Parcelable {
public final long startTime;
/** @hide */
- SyncInfo(int authorityId, Account account, String authority,
+ public SyncInfo(int authorityId, Account account, String authority,
long startTime) {
this.authorityId = authorityId;
this.account = account;
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
deleted file mode 100644
index e4b4b97..0000000
--- a/core/java/android/content/SyncManager.java
+++ /dev/null
@@ -1,2632 +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.content;
-
-import android.accounts.Account;
-import android.accounts.AccountAndUser;
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerService;
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.SyncStorageEngine.OnSyncRequestListener;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.content.pm.RegisteredServicesCache;
-import android.content.pm.RegisteredServicesCacheListener;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-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.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.WorkSource;
-import android.provider.Settings;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Slog;
-
-import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.IndentingPrintWriter;
-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;
-import java.util.Arrays;
-import java.util.Collection;
-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.Random;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * @hide
- */
-public class SyncManager {
- private static final String TAG = "SyncManager";
-
- /** Delay a sync due to local changes this long. In milliseconds */
- private static final long LOCAL_SYNC_DELAY;
-
- /**
- * If a sync takes longer than this and the sync queue is not empty then we will
- * cancel it and add it back to the end of the sync queue. In milliseconds.
- */
- private static final long MAX_TIME_PER_SYNC;
-
- static {
- final boolean isLargeRAM = ActivityManager.isLargeRAM();
- int defaultMaxInitSyncs = isLargeRAM ? 5 : 2;
- int defaultMaxRegularSyncs = isLargeRAM ? 2 : 1;
- MAX_SIMULTANEOUS_INITIALIZATION_SYNCS =
- SystemProperties.getInt("sync.max_init_syncs", defaultMaxInitSyncs);
- MAX_SIMULTANEOUS_REGULAR_SYNCS =
- SystemProperties.getInt("sync.max_regular_syncs", defaultMaxRegularSyncs);
- LOCAL_SYNC_DELAY =
- SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */);
- MAX_TIME_PER_SYNC =
- SystemProperties.getLong("sync.max_time_per_sync", 5 * 60 * 1000 /* 5 minutes */);
- SYNC_NOTIFICATION_DELAY =
- SystemProperties.getLong("sync.notification_delay", 30 * 1000 /* 30 seconds */);
- }
-
- private static final long SYNC_NOTIFICATION_DELAY;
-
- /**
- * When retrying a sync for the first time use this delay. After that
- * the retry time will double until it reached MAX_SYNC_RETRY_TIME.
- * In milliseconds.
- */
- private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds
-
- /**
- * Default the max sync retry time to this value.
- */
- private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour
-
- /**
- * How long to wait before retrying a sync that failed due to one already being in progress.
- */
- private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
-
- private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
-
- private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*";
- private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
- private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
-
- private static final int MAX_SIMULTANEOUS_REGULAR_SYNCS;
- private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS;
-
- private Context mContext;
-
- private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0];
-
- // TODO: add better locking around mRunningAccounts
- private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY;
-
- volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
- volatile private PowerManager.WakeLock mSyncManagerWakeLock;
- volatile private boolean mDataConnectionIsConnected = false;
- volatile private boolean mStorageIsLow = false;
-
- private final NotificationManager mNotificationMgr;
- private AlarmManager mAlarmService = null;
-
- private SyncStorageEngine mSyncStorageEngine;
-
- @GuardedBy("mSyncQueue")
- private final SyncQueue mSyncQueue;
-
- protected final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList();
-
- // set if the sync active indicator should be reported
- private boolean mNeedSyncActiveNotification = false;
-
- private final PendingIntent mSyncAlarmIntent;
- // Synchronized on "this". Instead of using this directly one should instead call
- // its accessor, getConnManager().
- private ConnectivityManager mConnManagerDoNotUseDirectly;
-
- protected SyncAdaptersCache mSyncAdapters;
-
- private BroadcastReceiver mStorageIntentReceiver =
- new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Internal storage is low.");
- }
- mStorageIsLow = true;
- 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)) {
- Log.v(TAG, "Internal storage is ok.");
- }
- mStorageIsLow = false;
- sendCheckAlarmsMessage();
- }
- }
- };
-
- private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- mSyncHandler.onBootCompleted();
- }
- };
-
- private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- if (getConnectivityManager().getBackgroundDataSetting()) {
- 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) {
- updateRunningAccounts();
-
- // Kick off sync for everyone, since this was a radical account change
- scheduleSync(null, UserHandle.USER_ALL, null, null, 0 /* no delay */, false);
- }
- };
-
- private final PowerManager mPowerManager;
-
- // Use this as a random offset to seed all periodic syncs
- private int mSyncRandomOffsetMillis;
-
- private final 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() {
- return mUserManager.getUsers();
- }
-
- private boolean containsAccountAndUser(AccountAndUser[] accounts, Account account, int userId) {
- boolean found = false;
- for (int i = 0; i < accounts.length; i++) {
- if (accounts[i].userId == userId
- && accounts[i].account.equals(account)) {
- found = true;
- break;
- }
- }
- return found;
- }
-
- public void updateRunningAccounts() {
- mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
-
- if (mBootCompleted) {
- doDatabaseCleanup();
- }
-
- for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
- if (!containsAccountAndUser(mRunningAccounts,
- currentSyncContext.mSyncOperation.account,
- currentSyncContext.mSyncOperation.userId)) {
- Log.d(TAG, "canceling sync since the account is no longer running");
- sendSyncFinishedOrCanceledMessage(currentSyncContext,
- null /* no result since this is a cancel */);
- }
- }
-
- // we must do this since we don't bother scheduling alarms when
- // the accounts are not set yet
- sendCheckAlarmsMessage();
- }
-
- private void doDatabaseCleanup() {
- for (UserInfo user : mUserManager.getUsers(true)) {
- // Skip any partially created/removed users
- if (user.partial) continue;
- Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(user.id);
- mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id);
- }
- }
-
- private BroadcastReceiver mConnectivityIntentReceiver =
- new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- final boolean wasConnected = mDataConnectionIsConnected;
-
- // don't use the intent to figure out if network is connected, just check
- // ConnectivityManager directly.
- mDataConnectionIsConnected = readDataConnectionState();
- if (mDataConnectionIsConnected) {
- if (!wasConnected) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Reconnection detected: clearing all backoffs");
- }
- mSyncStorageEngine.clearAllBackoffs(mSyncQueue);
- }
- sendCheckAlarmsMessage();
- }
- }
- };
-
- private boolean readDataConnectionState() {
- NetworkInfo networkInfo = getConnectivityManager().getActiveNetworkInfo();
- return (networkInfo != null) && networkInfo.isConnected();
- }
-
- private BroadcastReceiver mShutdownIntentReceiver =
- new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- Log.w(TAG, "Writing sync state before shutdown...");
- getSyncStorageEngine().writeAllState();
- }
- };
-
- private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId == UserHandle.USER_NULL) return;
-
- if (Intent.ACTION_USER_REMOVED.equals(action)) {
- onUserRemoved(userId);
- } else if (Intent.ACTION_USER_STARTING.equals(action)) {
- onUserStarting(userId);
- } else if (Intent.ACTION_USER_STOPPING.equals(action)) {
- onUserStopping(userId);
- }
- }
- };
-
- private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
- private final SyncHandler mSyncHandler;
-
- private volatile boolean mBootCompleted = false;
-
- private ConnectivityManager getConnectivityManager() {
- synchronized (this) {
- if (mConnManagerDoNotUseDirectly == null) {
- mConnManagerDoNotUseDirectly = (ConnectivityManager)mContext.getSystemService(
- Context.CONNECTIVITY_SERVICE);
- }
- return mConnManagerDoNotUseDirectly;
- }
- }
-
- /**
- * Should only be created after {@link ContentService#systemReady()} so that
- * {@link PackageManager} is ready to query.
- */
- public SyncManager(Context context, boolean factoryTest) {
- // Initialize the SyncStorageEngine first, before registering observers
- // and creating threads and so on; it may fail if the disk is full.
- mContext = context;
-
- SyncStorageEngine.init(context);
- mSyncStorageEngine = SyncStorageEngine.getSingleton();
- mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
- public void onSyncRequest(Account account, int userId, String authority,
- Bundle extras) {
- scheduleSync(account, userId, authority, extras, 0, false);
- }
- });
-
- mSyncAdapters = new SyncAdaptersCache(mContext);
- mSyncQueue = new SyncQueue(mSyncStorageEngine, mSyncAdapters);
-
- HandlerThread syncThread = new HandlerThread("SyncHandlerThread",
- Process.THREAD_PRIORITY_BACKGROUND);
- syncThread.start();
- mSyncHandler = new SyncHandler(syncThread.getLooper());
-
- mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() {
- @Override
- public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) {
- if (!removed) {
- scheduleSync(null, UserHandle.USER_ALL, type.authority, null, 0 /* no delay */,
- false /* onlyThoseWithUnkownSyncableState */);
- }
- }
- }, mSyncHandler);
-
- mSyncAlarmIntent = PendingIntent.getBroadcast(
- mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
-
- IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
- context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
-
- if (!factoryTest) {
- intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
- context.registerReceiver(mBootCompletedReceiver, intentFilter);
- }
-
- intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
- context.registerReceiver(mBackgroundDataSettingChanged, intentFilter);
-
- intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
- intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
- context.registerReceiver(mStorageIntentReceiver, intentFilter);
-
- intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
- intentFilter.setPriority(100);
- context.registerReceiver(mShutdownIntentReceiver, intentFilter);
-
- intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_USER_REMOVED);
- intentFilter.addAction(Intent.ACTION_USER_STARTING);
- intentFilter.addAction(Intent.ACTION_USER_STOPPING);
- mContext.registerReceiverAsUser(
- mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
-
- if (!factoryTest) {
- mNotificationMgr = (NotificationManager)
- context.getSystemService(Context.NOTIFICATION_SERVICE);
- context.registerReceiver(new SyncAlarmIntentReceiver(),
- new IntentFilter(ACTION_SYNC_ALARM));
- } else {
- mNotificationMgr = null;
- }
- mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-
- // This WakeLock is used to ensure that we stay awake between the time that we receive
- // a sync alarm notification and when we finish processing it. We need to do this
- // because we don't do the work in the alarm handler, rather we do it in a message
- // handler.
- mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- HANDLE_SYNC_ALARM_WAKE_LOCK);
- mHandleAlarmWakeLock.setReferenceCounted(false);
-
- // This WakeLock is used to ensure that we stay awake while running the sync loop
- // message handler. Normally we will hold a sync adapter wake lock while it is being
- // synced but during the execution of the sync loop it might finish a sync for
- // one sync adapter before starting the sync for the other sync adapter and we
- // don't want the device to go to sleep during that window.
- mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- SYNC_LOOP_WAKE_LOCK);
- mSyncManagerWakeLock.setReferenceCounted(false);
-
- mSyncStorageEngine.addStatusChangeListener(
- ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
- public void onStatusChanged(int which) {
- // force the sync loop to run if the settings change
- sendCheckAlarmsMessage();
- }
- });
-
- if (!factoryTest) {
- // Register for account list updates for all users
- mContext.registerReceiverAsUser(mAccountsUpdatedReceiver,
- UserHandle.ALL,
- new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION),
- null, null);
- }
-
- // Pick a random second in a day to seed all periodic syncs
- mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000;
- }
-
- /**
- * Return a random value v that satisfies minValue <= v < maxValue. The difference between
- * maxValue and minValue must be less than Integer.MAX_VALUE.
- */
- private long jitterize(long minValue, long maxValue) {
- Random random = new Random(SystemClock.elapsedRealtime());
- long spread = maxValue - minValue;
- if (spread > Integer.MAX_VALUE) {
- throw new IllegalArgumentException("the difference between the maxValue and the "
- + "minValue must be less than " + Integer.MAX_VALUE);
- }
- return minValue + random.nextInt((int)spread);
- }
-
- public SyncStorageEngine getSyncStorageEngine() {
- return mSyncStorageEngine;
- }
-
- private void ensureAlarmService() {
- if (mAlarmService == null) {
- mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
- }
- }
-
- /**
- * Initiate a sync. This can start a sync for all providers
- * (pass null to url, set onlyTicklable to false), only those
- * providers that are marked as ticklable (pass null to url,
- * set onlyTicklable to true), or a specific provider (set url
- * to the content url of the provider).
- *
- * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is
- * true then initiate a sync that just checks for local changes to send
- * to the server, otherwise initiate a sync that first gets any
- * changes from the server before sending local changes back to
- * the server.
- *
- * <p>If a specific provider is being synced (the url is non-null)
- * then the extras can contain SyncAdapter-specific information
- * to control what gets synced (e.g. which specific feed to sync).
- *
- * <p>You'll start getting callbacks after this.
- *
- * @param requestedAccount the account to sync, may be null to signify all accounts
- * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL,
- * then all users' accounts are considered.
- * @param requestedAuthority the authority to sync, may be null to indicate all authorities
- * @param extras a Map of SyncAdapter-specific information to control
- * syncs of a specific provider. Can be null. Is ignored
- * if the url is null.
- * @param delay how many milliseconds in the future to wait before performing this
- * @param onlyThoseWithUnkownSyncableState
- */
- public void scheduleSync(Account requestedAccount, int userId, String requestedAuthority,
- Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) {
- boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
-
- final boolean backgroundDataUsageAllowed = !mBootCompleted ||
- getConnectivityManager().getBackgroundDataSetting();
-
- if (extras == null) extras = new Bundle();
-
- Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
- if (expedited) {
- delay = -1; // this means schedule at the front of the queue
- }
-
- AccountAndUser[] accounts;
- 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
- // sync request
- accounts = mRunningAccounts;
- if (accounts.length == 0) {
- if (isLoggable) {
- Log.v(TAG, "scheduleSync: no accounts configured, dropping");
- }
- return;
- }
- }
-
- final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
- final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
- if (manualSync) {
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
- }
- final boolean ignoreSettings =
- extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
-
- int source;
- if (uploadOnly) {
- source = SyncStorageEngine.SOURCE_LOCAL;
- } else if (manualSync) {
- source = SyncStorageEngine.SOURCE_USER;
- } else if (requestedAuthority == null) {
- source = SyncStorageEngine.SOURCE_POLL;
- } else {
- // this isn't strictly server, since arbitrary callers can (and do) request
- // a non-forced two-way sync on a specific url
- source = SyncStorageEngine.SOURCE_SERVER;
- }
-
- for (AccountAndUser account : accounts) {
- // Compile a list of authorities that have sync adapters.
- // For each authority sync each account that matches a sync adapter.
- final HashSet<String> syncableAuthorities = new HashSet<String>();
- for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter :
- mSyncAdapters.getAllServices(account.userId)) {
- syncableAuthorities.add(syncAdapter.type.authority);
- }
-
- // if the url was specified then replace the list of authorities
- // with just this authority or clear it if this authority isn't
- // syncable
- if (requestedAuthority != null) {
- final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority);
- syncableAuthorities.clear();
- if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority);
- }
-
- for (String authority : syncableAuthorities) {
- int isSyncable = mSyncStorageEngine.getIsSyncable(account.account, account.userId,
- authority);
- if (isSyncable == 0) {
- continue;
- }
- final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
- syncAdapterInfo = mSyncAdapters.getServiceInfo(
- SyncAdapterType.newKey(authority, account.account.type), account.userId);
- if (syncAdapterInfo == null) {
- continue;
- }
- final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
- final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
- if (isSyncable < 0 && isAlwaysSyncable) {
- mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1);
- isSyncable = 1;
- }
- if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
- continue;
- }
- if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) {
- continue;
- }
-
- // always allow if the isSyncable state is unknown
- boolean syncAllowed =
- (isSyncable < 0)
- || ignoreSettings
- || (backgroundDataUsageAllowed
- && mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
- && mSyncStorageEngine.getSyncAutomatically(account.account,
- account.userId, authority));
- if (!syncAllowed) {
- if (isLoggable) {
- Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
- + " is not allowed, dropping request");
- }
- continue;
- }
-
- Pair<Long, Long> backoff = mSyncStorageEngine
- .getBackoff(account.account, account.userId, authority);
- long delayUntil = mSyncStorageEngine.getDelayUntilTime(account.account,
- account.userId, authority);
- final long backoffTime = backoff != null ? backoff.first : 0;
- if (isSyncable < 0) {
- Bundle newExtras = new Bundle();
- newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
- if (isLoggable) {
- Log.v(TAG, "scheduleSync:"
- + " delay " + delay
- + ", source " + source
- + ", account " + account
- + ", authority " + authority
- + ", extras " + newExtras);
- }
- scheduleSyncOperation(
- new SyncOperation(account.account, account.userId, source, authority,
- newExtras, 0, backoffTime, delayUntil, allowParallelSyncs));
- }
- if (!onlyThoseWithUnkownSyncableState) {
- if (isLoggable) {
- Log.v(TAG, "scheduleSync:"
- + " delay " + delay
- + ", source " + source
- + ", account " + account
- + ", authority " + authority
- + ", extras " + extras);
- }
- scheduleSyncOperation(
- new SyncOperation(account.account, account.userId, source, authority,
- extras, delay, backoffTime, delayUntil, allowParallelSyncs));
- }
- }
- }
- }
-
- public void scheduleLocalSync(Account account, int userId, String authority) {
- final Bundle extras = new Bundle();
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
- scheduleSync(account, userId, authority, extras, LOCAL_SYNC_DELAY,
- false /* onlyThoseWithUnkownSyncableState */);
- }
-
- public SyncAdapterType[] getSyncAdapterTypes(int userId) {
- final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos;
- serviceInfos = mSyncAdapters.getAllServices(userId);
- SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()];
- int i = 0;
- for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) {
- types[i] = serviceInfo.type;
- ++i;
- }
- return types;
- }
-
- private void sendSyncAlarmMessage() {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
- mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
- }
-
- private void sendCheckAlarmsMessage() {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
- mSyncHandler.removeMessages(SyncHandler.MESSAGE_CHECK_ALARMS);
- mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
- }
-
- private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext,
- SyncResult syncResult) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED");
- Message msg = mSyncHandler.obtainMessage();
- msg.what = SyncHandler.MESSAGE_SYNC_FINISHED;
- msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult);
- mSyncHandler.sendMessage(msg);
- }
-
- private void sendCancelSyncsMessage(final Account account, final int userId,
- final String authority) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL");
- Message msg = mSyncHandler.obtainMessage();
- msg.what = SyncHandler.MESSAGE_CANCEL;
- msg.obj = Pair.create(account, authority);
- msg.arg1 = userId;
- mSyncHandler.sendMessage(msg);
- }
-
- class SyncHandlerMessagePayload {
- public final ActiveSyncContext activeSyncContext;
- public final SyncResult syncResult;
-
- SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) {
- this.activeSyncContext = syncContext;
- this.syncResult = syncResult;
- }
- }
-
- class SyncAlarmIntentReceiver extends BroadcastReceiver {
- public void onReceive(Context context, Intent intent) {
- mHandleAlarmWakeLock.acquire();
- sendSyncAlarmMessage();
- }
- }
-
- private void clearBackoffSetting(SyncOperation op) {
- mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
- SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
- synchronized (mSyncQueue) {
- mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, 0);
- }
- }
-
- private void increaseBackoffSetting(SyncOperation op) {
- // TODO: Use this function to align it to an already scheduled sync
- // operation in the specified window
- final long now = SystemClock.elapsedRealtime();
-
- final Pair<Long, Long> previousSettings =
- mSyncStorageEngine.getBackoff(op.account, op.userId, op.authority);
- long newDelayInMs = -1;
- if (previousSettings != null) {
- // don't increase backoff before current backoff is expired. This will happen for op's
- // with ignoreBackoff set.
- if (now < previousSettings.first) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Still in backoff, do not increase it. "
- + "Remaining: " + ((previousSettings.first - now) / 1000) + " seconds.");
- }
- return;
- }
- // Subsequent delays are the double of the previous delay
- newDelayInMs = previousSettings.second * 2;
- }
- if (newDelayInMs <= 0) {
- // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
- newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
- (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
- }
-
- // Cap the delay
- 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;
- }
-
- final long backoff = now + newDelayInMs;
-
- mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
- backoff, newDelayInMs);
-
- op.backoff = backoff;
- op.updateEffectiveRunTime();
-
- synchronized (mSyncQueue) {
- mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, backoff);
- }
- }
-
- private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
- final long delayUntil = delayUntilSeconds * 1000;
- final long absoluteNow = System.currentTimeMillis();
- long newDelayUntilTime;
- if (delayUntil > absoluteNow) {
- newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow);
- } else {
- newDelayUntilTime = 0;
- }
- mSyncStorageEngine
- .setDelayUntilTime(op.account, op.userId, op.authority, newDelayUntilTime);
- synchronized (mSyncQueue) {
- mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime);
- }
- }
-
- /**
- * Cancel the active sync if it matches the authority and account.
- * @param account limit the cancelations to syncs with this account, if non-null
- * @param authority limit the cancelations to syncs with this authority, if non-null
- */
- public void cancelActiveSync(Account account, int userId, String authority) {
- sendCancelSyncsMessage(account, userId, authority);
- }
-
- /**
- * Create and schedule a SyncOperation.
- *
- * @param syncOperation the SyncOperation to schedule
- */
- public void scheduleSyncOperation(SyncOperation syncOperation) {
- boolean queueChanged;
- synchronized (mSyncQueue) {
- queueChanged = mSyncQueue.add(syncOperation);
- }
-
- if (queueChanged) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
- }
- sendCheckAlarmsMessage();
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation "
- + syncOperation);
- }
- }
- }
-
- /**
- * Remove scheduled sync operations.
- * @param account limit the removals to operations with this account, if non-null
- * @param authority limit the removals to operations with this authority, if non-null
- */
- public void clearScheduledSyncOperations(Account account, int userId, String authority) {
- synchronized (mSyncQueue) {
- mSyncQueue.remove(account, userId, authority);
- }
- mSyncStorageEngine.setBackoff(account, userId, authority,
- SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
- }
-
- void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
- boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
- if (isLoggable) {
- Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
- }
-
- operation = new SyncOperation(operation);
-
- // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
- // request. Retries of the request will always honor the backoff, so clear the
- // flag in case we retry this request.
- if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
- operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
- }
-
- // If this sync aborted because the internal sync loop retried too many times then
- // don't reschedule. Otherwise we risk getting into a retry loop.
- // If the operation succeeded to some extent then retry immediately.
- // If this was a two-way sync then retry soft errors with an exponential backoff.
- // If this was an upward sync then schedule a two-way sync immediately.
- // Otherwise do not reschedule.
- if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
- Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
- + operation);
- } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)
- && !syncResult.syncAlreadyInProgress) {
- operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
- Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
- + "encountered an error: " + operation);
- scheduleSyncOperation(operation);
- } else if (syncResult.tooManyRetries) {
- Log.d(TAG, "not retrying sync operation because it retried too many times: "
- + operation);
- } else if (syncResult.madeSomeProgress()) {
- if (isLoggable) {
- Log.d(TAG, "retrying sync operation because even though it had an error "
- + "it achieved some success");
- }
- scheduleSyncOperation(operation);
- } else if (syncResult.syncAlreadyInProgress) {
- if (isLoggable) {
- Log.d(TAG, "retrying sync operation that failed because there was already a "
- + "sync in progress: " + operation);
- }
- scheduleSyncOperation(new SyncOperation(operation.account, operation.userId,
- operation.syncSource,
- operation.authority, operation.extras,
- DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000,
- operation.backoff, operation.delayUntil, operation.allowParallelSyncs));
- } else if (syncResult.hasSoftError()) {
- if (isLoggable) {
- Log.d(TAG, "retrying sync operation because it encountered a soft error: "
- + operation);
- }
- scheduleSyncOperation(operation);
- } else {
- Log.d(TAG, "not retrying sync operation because the error is a hard error: "
- + operation);
- }
- }
-
- private void onUserStarting(int userId) {
- // Make sure that accounts we're about to use are valid
- AccountManagerService.getSingleton().validateAccounts(userId);
-
- mSyncAdapters.invalidateCache(userId);
-
- updateRunningAccounts();
-
- synchronized (mSyncQueue) {
- mSyncQueue.addPendingOperations(userId);
- }
-
- // Schedule sync for any accounts under started user
- final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId);
- for (Account account : accounts) {
- scheduleSync(account, userId, null, null, 0 /* no delay */,
- true /* onlyThoseWithUnknownSyncableState */);
- }
-
- sendCheckAlarmsMessage();
- }
-
- private void onUserStopping(int userId) {
- updateRunningAccounts();
-
- cancelActiveSync(
- null /* any account */,
- userId,
- null /* any authority */);
- }
-
- private void onUserRemoved(int userId) {
- updateRunningAccounts();
-
- // Clean up the storage engine database
- mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
- synchronized (mSyncQueue) {
- mSyncQueue.removeUser(userId);
- }
- }
-
- /**
- * @hide
- */
- class ActiveSyncContext extends ISyncContext.Stub
- implements ServiceConnection, IBinder.DeathRecipient {
- final SyncOperation mSyncOperation;
- final long mHistoryRowId;
- ISyncAdapter mSyncAdapter;
- final long mStartTime;
- long mTimeoutStartTime;
- boolean mBound;
- final PowerManager.WakeLock mSyncWakeLock;
- final int mSyncAdapterUid;
- SyncInfo mSyncInfo;
- boolean mIsLinkedToDeath = false;
-
- /**
- * Create an ActiveSyncContext for an impending sync and grab the wakelock for that
- * sync adapter. Since this grabs the wakelock you need to be sure to call
- * close() when you are done with this ActiveSyncContext, whether the sync succeeded
- * or not.
- * @param syncOperation the SyncOperation we are about to sync
- * @param historyRowId the row in which to record the history info for this sync
- * @param syncAdapterUid the UID of the application that contains the sync adapter
- * for this sync. This is used to attribute the wakelock hold to that application.
- */
- public ActiveSyncContext(SyncOperation syncOperation, long historyRowId,
- int syncAdapterUid) {
- super();
- mSyncAdapterUid = syncAdapterUid;
- mSyncOperation = syncOperation;
- mHistoryRowId = historyRowId;
- mSyncAdapter = null;
- mStartTime = SystemClock.elapsedRealtime();
- mTimeoutStartTime = mStartTime;
- mSyncWakeLock = mSyncHandler.getSyncWakeLock(
- mSyncOperation.account, mSyncOperation.authority);
- mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid));
- mSyncWakeLock.acquire();
- }
-
- public void sendHeartbeat() {
- // heartbeats are no longer used
- }
-
- public void onFinished(SyncResult result) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "onFinished: " + this);
- // include "this" in the message so that the handler can ignore it if this
- // ActiveSyncContext is no longer the mActiveSyncContext at message handling
- // time
- sendSyncFinishedOrCanceledMessage(this, result);
- }
-
- public void toString(StringBuilder sb) {
- sb.append("startTime ").append(mStartTime)
- .append(", mTimeoutStartTime ").append(mTimeoutStartTime)
- .append(", mHistoryRowId ").append(mHistoryRowId)
- .append(", syncOperation ").append(mSyncOperation);
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- Message msg = mSyncHandler.obtainMessage();
- msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED;
- msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service));
- mSyncHandler.sendMessage(msg);
- }
-
- public void onServiceDisconnected(ComponentName name) {
- Message msg = mSyncHandler.obtainMessage();
- msg.what = SyncHandler.MESSAGE_SERVICE_DISCONNECTED;
- msg.obj = new ServiceConnectionData(this, null);
- mSyncHandler.sendMessage(msg);
- }
-
- boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info, int userId) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this);
- }
- Intent intent = new Intent();
- intent.setAction("android.content.SyncAdapter");
- 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.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
- | Context.BIND_ALLOW_OOM_MANAGEMENT,
- mSyncOperation.userId);
- if (!bindResult) {
- mBound = false;
- }
- return bindResult;
- }
-
- /**
- * Performs the required cleanup, which is the releasing of the wakelock and
- * unbinding from the sync adapter (if actually bound).
- */
- protected void close() {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
- }
- if (mBound) {
- mBound = false;
- mContext.unbindService(this);
- }
- mSyncWakeLock.release();
- mSyncWakeLock.setWorkSource(null);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- toString(sb);
- return sb.toString();
- }
-
- @Override
- public void binderDied() {
- sendSyncFinishedOrCanceledMessage(this, null);
- }
- }
-
- protected void dump(FileDescriptor fd, PrintWriter pw) {
- final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- dumpSyncState(ipw);
- dumpSyncHistory(ipw);
- dumpSyncAdapters(ipw);
- }
-
- static String formatTime(long time) {
- Time tobj = new Time();
- tobj.set(time);
- return tobj.format("%Y-%m-%d %H:%M:%S");
- }
-
- protected void dumpSyncState(PrintWriter pw) {
- pw.print("data connected: "); pw.println(mDataConnectionIsConnected);
- pw.print("auto sync: ");
- List<UserInfo> users = getAllUsers();
- if (users != null) {
- for (UserInfo user : users) {
- pw.print("u" + user.id + "="
- + mSyncStorageEngine.getMasterSyncAutomatically(user.id) + " ");
- }
- pw.println();
- }
- pw.print("memory low: "); pw.println(mStorageIsLow);
-
- final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
-
- pw.print("accounts: ");
- if (accounts != INITIAL_ACCOUNTS_ARRAY) {
- pw.println(accounts.length);
- } else {
- pw.println("not known yet");
- }
- final long now = SystemClock.elapsedRealtime();
- pw.print("now: "); pw.print(now);
- pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
- pw.print("offset: "); pw.print(DateUtils.formatElapsedTime(mSyncRandomOffsetMillis/1000));
- pw.println(" (HH:MM:SS)");
- pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
- pw.println(" (HH:MM:SS)");
- pw.print("time spent syncing: ");
- pw.print(DateUtils.formatElapsedTime(
- mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000));
- pw.print(" (HH:MM:SS), sync ");
- pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ");
- pw.println("in progress");
- if (mSyncHandler.mAlarmScheduleTime != null) {
- pw.print("next alarm time: "); pw.print(mSyncHandler.mAlarmScheduleTime);
- pw.print(" (");
- pw.print(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000));
- pw.println(" (HH:MM:SS) from now)");
- } else {
- pw.println("no alarm is scheduled (there had better not be any pending syncs)");
- }
-
- pw.print("notification info: ");
- final StringBuilder sb = new StringBuilder();
- mSyncHandler.mSyncNotificationInfo.toString(sb);
- pw.println(sb.toString());
-
- pw.println();
- pw.println("Active Syncs: " + mActiveSyncContexts.size());
- for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
- final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000;
- pw.print(" ");
- pw.print(DateUtils.formatElapsedTime(durationInSeconds));
- pw.print(" - ");
- pw.print(activeSyncContext.mSyncOperation.dump(false));
- pw.println();
- }
-
- synchronized (mSyncQueue) {
- sb.setLength(0);
- mSyncQueue.dump(sb);
- }
- pw.println();
- pw.print(sb.toString());
-
- // join the installed sync adapter with the accounts list and emit for everything
- pw.println();
- pw.println("Sync Status");
- for (AccountAndUser account : accounts) {
- pw.print(" Account "); pw.print(account.account.name);
- pw.print(" u"); pw.print(account.userId);
- pw.print(" "); pw.print(account.account.type);
- pw.println(":");
- for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType :
- mSyncAdapters.getAllServices(account.userId)) {
- if (!syncAdapterType.type.accountType.equals(account.account.type)) {
- continue;
- }
-
- SyncStorageEngine.AuthorityInfo settings =
- mSyncStorageEngine.getOrCreateAuthority(
- account.account, account.userId, syncAdapterType.type.authority);
- SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings);
- pw.print(" "); pw.print(settings.authority);
- pw.println(":");
- pw.print(" settings:");
- pw.print(" " + (settings.syncable > 0
- ? "syncable"
- : (settings.syncable == 0 ? "not syncable" : "not initialized")));
- pw.print(", " + (settings.enabled ? "enabled" : "disabled"));
- if (settings.delayUntil > now) {
- pw.print(", delay for "
- + ((settings.delayUntil - now) / 1000) + " sec");
- }
- if (settings.backoffTime > now) {
- pw.print(", backoff for "
- + ((settings.backoffTime - now) / 1000) + " sec");
- }
- if (settings.backoffDelay > 0) {
- pw.print(", the backoff increment is " + settings.backoffDelay / 1000
- + " sec");
- }
- pw.println();
- for (int periodicIndex = 0;
- periodicIndex < settings.periodicSyncs.size();
- periodicIndex++) {
- Pair<Bundle, Long> info = settings.periodicSyncs.get(periodicIndex);
- long lastPeriodicTime = status.getPeriodicSyncTime(periodicIndex);
- long nextPeriodicTime = lastPeriodicTime + info.second * 1000;
- pw.println(" periodic period=" + info.second
- + ", extras=" + info.first
- + ", next=" + formatTime(nextPeriodicTime));
- }
- pw.print(" count: local="); pw.print(status.numSourceLocal);
- pw.print(" poll="); pw.print(status.numSourcePoll);
- pw.print(" periodic="); pw.print(status.numSourcePeriodic);
- pw.print(" server="); pw.print(status.numSourceServer);
- pw.print(" user="); pw.print(status.numSourceUser);
- pw.print(" total="); pw.print(status.numSyncs);
- pw.println();
- pw.print(" total duration: ");
- pw.println(DateUtils.formatElapsedTime(status.totalElapsedTime/1000));
- if (status.lastSuccessTime != 0) {
- pw.print(" SUCCESS: source=");
- pw.print(SyncStorageEngine.SOURCES[status.lastSuccessSource]);
- pw.print(" time=");
- pw.println(formatTime(status.lastSuccessTime));
- }
- if (status.lastFailureTime != 0) {
- pw.print(" FAILURE: source=");
- pw.print(SyncStorageEngine.SOURCES[
- status.lastFailureSource]);
- pw.print(" initialTime=");
- pw.print(formatTime(status.initialFailureTime));
- pw.print(" lastTime=");
- pw.println(formatTime(status.lastFailureTime));
- int errCode = status.getLastFailureMesgAsInt(0);
- pw.print(" message: "); pw.println(
- getLastFailureMessage(errCode) + " (" + errCode + ")");
- }
- }
- }
- }
-
- private String getLastFailureMessage(int code) {
- switch (code) {
- case ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS:
- return "sync already in progress";
-
- case ContentResolver.SYNC_ERROR_AUTHENTICATION:
- return "authentication error";
-
- case ContentResolver.SYNC_ERROR_IO:
- return "I/O error";
-
- case ContentResolver.SYNC_ERROR_PARSE:
- return "parse error";
-
- case ContentResolver.SYNC_ERROR_CONFLICT:
- return "conflict error";
-
- case ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS:
- return "too many deletions error";
-
- case ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES:
- return "too many retries error";
-
- case ContentResolver.SYNC_ERROR_INTERNAL:
- return "internal error";
-
- default:
- return "unknown";
- }
- }
-
- private void dumpTimeSec(PrintWriter pw, long time) {
- pw.print(time/1000); pw.print('.'); pw.print((time/100)%10);
- pw.print('s');
- }
-
- private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) {
- pw.print("Success ("); pw.print(ds.successCount);
- if (ds.successCount > 0) {
- pw.print(" for "); dumpTimeSec(pw, ds.successTime);
- pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount);
- }
- pw.print(") Failure ("); pw.print(ds.failureCount);
- if (ds.failureCount > 0) {
- pw.print(" for "); dumpTimeSec(pw, ds.failureTime);
- pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount);
- }
- pw.println(")");
- }
-
- protected void dumpSyncHistory(PrintWriter pw) {
- dumpRecentHistory(pw);
- dumpDayStatistics(pw);
- }
-
- private void dumpRecentHistory(PrintWriter pw) {
- final ArrayList<SyncStorageEngine.SyncHistoryItem> items
- = mSyncStorageEngine.getSyncHistory();
- if (items != null && items.size() > 0) {
- final Map<String, AuthoritySyncStats> authorityMap = Maps.newHashMap();
- long totalElapsedTime = 0;
- long totalTimes = 0;
- final int N = items.size();
-
- int maxAuthority = 0;
- int maxAccount = 0;
- for (SyncStorageEngine.SyncHistoryItem item : items) {
- SyncStorageEngine.AuthorityInfo authority
- = mSyncStorageEngine.getAuthority(item.authorityId);
- final String authorityName;
- final String accountKey;
- if (authority != null) {
- authorityName = authority.authority;
- accountKey = authority.account.name + "/" + authority.account.type
- + " u" + authority.userId;
- } else {
- authorityName = "Unknown";
- accountKey = "Unknown";
- }
-
- int length = authorityName.length();
- if (length > maxAuthority) {
- maxAuthority = length;
- }
- length = accountKey.length();
- if (length > maxAccount) {
- maxAccount = length;
- }
-
- final long elapsedTime = item.elapsedTime;
- totalElapsedTime += elapsedTime;
- totalTimes++;
- AuthoritySyncStats authoritySyncStats = authorityMap.get(authorityName);
- if (authoritySyncStats == null) {
- authoritySyncStats = new AuthoritySyncStats(authorityName);
- authorityMap.put(authorityName, authoritySyncStats);
- }
- authoritySyncStats.elapsedTime += elapsedTime;
- authoritySyncStats.times++;
- final Map<String, AccountSyncStats> accountMap = authoritySyncStats.accountMap;
- AccountSyncStats accountSyncStats = accountMap.get(accountKey);
- if (accountSyncStats == null) {
- accountSyncStats = new AccountSyncStats(accountKey);
- accountMap.put(accountKey, accountSyncStats);
- }
- accountSyncStats.elapsedTime += elapsedTime;
- accountSyncStats.times++;
-
- }
-
- if (totalElapsedTime > 0) {
- pw.println();
- pw.printf("Detailed Statistics (Recent history): "
- + "%d (# of times) %ds (sync time)\n",
- totalTimes, totalElapsedTime / 1000);
-
- final List<AuthoritySyncStats> sortedAuthorities =
- new ArrayList<AuthoritySyncStats>(authorityMap.values());
- Collections.sort(sortedAuthorities, new Comparator<AuthoritySyncStats>() {
- @Override
- public int compare(AuthoritySyncStats lhs, AuthoritySyncStats rhs) {
- // reverse order
- int compare = Integer.compare(rhs.times, lhs.times);
- if (compare == 0) {
- compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime);
- }
- return compare;
- }
- });
-
- final int maxLength = Math.max(maxAuthority, maxAccount + 3);
- final int padLength = 2 + 2 + maxLength + 2 + 10 + 11;
- final char chars[] = new char[padLength];
- Arrays.fill(chars, '-');
- final String separator = new String(chars);
-
- final String authorityFormat =
- String.format(" %%-%ds: %%-9s %%-11s\n", maxLength + 2);
- final String accountFormat =
- String.format(" %%-%ds: %%-9s %%-11s\n", maxLength);
-
- pw.println(separator);
- for (AuthoritySyncStats authoritySyncStats : sortedAuthorities) {
- String name = authoritySyncStats.name;
- long elapsedTime;
- int times;
- String timeStr;
- String timesStr;
-
- elapsedTime = authoritySyncStats.elapsedTime;
- times = authoritySyncStats.times;
- timeStr = String.format("%ds/%d%%",
- elapsedTime / 1000,
- elapsedTime * 100 / totalElapsedTime);
- timesStr = String.format("%d/%d%%",
- times,
- times * 100 / totalTimes);
- pw.printf(authorityFormat, name, timesStr, timeStr);
-
- final List<AccountSyncStats> sortedAccounts =
- new ArrayList<AccountSyncStats>(
- authoritySyncStats.accountMap.values());
- Collections.sort(sortedAccounts, new Comparator<AccountSyncStats>() {
- @Override
- public int compare(AccountSyncStats lhs, AccountSyncStats rhs) {
- // reverse order
- int compare = Integer.compare(rhs.times, lhs.times);
- if (compare == 0) {
- compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime);
- }
- return compare;
- }
- });
- for (AccountSyncStats stats: sortedAccounts) {
- elapsedTime = stats.elapsedTime;
- times = stats.times;
- timeStr = String.format("%ds/%d%%",
- elapsedTime / 1000,
- elapsedTime * 100 / totalElapsedTime);
- timesStr = String.format("%d/%d%%",
- times,
- times * 100 / totalTimes);
- pw.printf(accountFormat, stats.name, timesStr, timeStr);
- }
- pw.println(separator);
- }
- }
-
- pw.println();
- pw.println("Recent Sync History");
- final String format = " %-" + maxAccount + "s %s\n";
- final Map<String, Long> lastTimeMap = Maps.newHashMap();
-
- for (int i = 0; i < N; i++) {
- SyncStorageEngine.SyncHistoryItem item = items.get(i);
- SyncStorageEngine.AuthorityInfo authority
- = mSyncStorageEngine.getAuthority(item.authorityId);
- final String authorityName;
- final String accountKey;
- if (authority != null) {
- authorityName = authority.authority;
- accountKey = authority.account.name + "/" + authority.account.type
- + " u" + authority.userId;
- } else {
- authorityName = "Unknown";
- accountKey = "Unknown";
- }
- final long elapsedTime = item.elapsedTime;
- final Time time = new Time();
- final long eventTime = item.eventTime;
- time.set(eventTime);
-
- final String key = authorityName + "/" + accountKey;
- final Long lastEventTime = lastTimeMap.get(key);
- final String diffString;
- if (lastEventTime == null) {
- diffString = "";
- } else {
- final long diff = (lastEventTime - eventTime) / 1000;
- if (diff < 60) {
- diffString = String.valueOf(diff);
- } else if (diff < 3600) {
- diffString = String.format("%02d:%02d", diff / 60, diff % 60);
- } else {
- final long sec = diff % 3600;
- diffString = String.format("%02d:%02d:%02d",
- diff / 3600, sec / 60, sec % 60);
- }
- }
- lastTimeMap.put(key, eventTime);
-
- pw.printf(" #%-3d: %s %8s %5.1fs %8s",
- i + 1,
- formatTime(eventTime),
- SyncStorageEngine.SOURCES[item.source],
- ((float) elapsedTime) / 1000,
- diffString);
- pw.printf(format, accountKey, authorityName);
-
- if (item.event != SyncStorageEngine.EVENT_STOP
- || item.upstreamActivity != 0
- || item.downstreamActivity != 0) {
- pw.printf(" event=%d upstreamActivity=%d downstreamActivity=%d\n",
- item.event,
- item.upstreamActivity,
- item.downstreamActivity);
- }
- if (item.mesg != null
- && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) {
- pw.printf(" mesg=%s\n", item.mesg);
- }
- }
- }
- }
-
- private void dumpDayStatistics(PrintWriter pw) {
- SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics();
- if (dses != null && dses[0] != null) {
- pw.println();
- pw.println("Sync Statistics");
- pw.print(" Today: "); dumpDayStatistic(pw, dses[0]);
- int today = dses[0].day;
- int i;
- SyncStorageEngine.DayStats ds;
-
- // Print each day in the current week.
- for (i=1; i<=6 && i < dses.length; i++) {
- ds = dses[i];
- if (ds == null) break;
- int delta = today-ds.day;
- if (delta > 6) break;
-
- pw.print(" Day-"); pw.print(delta); pw.print(": ");
- dumpDayStatistic(pw, ds);
- }
-
- // Aggregate all following days into weeks and print totals.
- int weekDay = today;
- while (i < dses.length) {
- SyncStorageEngine.DayStats aggr = null;
- weekDay -= 7;
- while (i < dses.length) {
- ds = dses[i];
- if (ds == null) {
- i = dses.length;
- break;
- }
- int delta = weekDay-ds.day;
- if (delta > 6) break;
- i++;
-
- if (aggr == null) {
- aggr = new SyncStorageEngine.DayStats(weekDay);
- }
- aggr.successCount += ds.successCount;
- aggr.successTime += ds.successTime;
- aggr.failureCount += ds.failureCount;
- aggr.failureTime += ds.failureTime;
- }
- if (aggr != null) {
- pw.print(" Week-"); pw.print((today-weekDay)/7); pw.print(": ");
- dumpDayStatistic(pw, aggr);
- }
- }
- }
- }
-
- private void dumpSyncAdapters(IndentingPrintWriter pw) {
- pw.println();
- final List<UserInfo> users = getAllUsers();
- if (users != null) {
- for (UserInfo user : users) {
- pw.println("Sync adapters for " + user + ":");
- pw.increaseIndent();
- for (RegisteredServicesCache.ServiceInfo<?> info :
- mSyncAdapters.getAllServices(user.id)) {
- pw.println(info);
- }
- pw.decreaseIndent();
- pw.println();
- }
- }
- }
-
- private static class AuthoritySyncStats {
- String name;
- long elapsedTime;
- int times;
- Map<String, AccountSyncStats> accountMap = Maps.newHashMap();
-
- private AuthoritySyncStats(String name) {
- this.name = name;
- }
- }
-
- private static class AccountSyncStats {
- String name;
- long elapsedTime;
- int times;
-
- private AccountSyncStats(String name) {
- this.name = name;
- }
- }
-
- /**
- * A helper object to keep track of the time we have spent syncing since the last boot
- */
- private class SyncTimeTracker {
- /** True if a sync was in progress on the most recent call to update() */
- boolean mLastWasSyncing = false;
- /** Used to track when lastWasSyncing was last set */
- long mWhenSyncStarted = 0;
- /** The cumulative time we have spent syncing */
- private long mTimeSpentSyncing;
-
- /** Call to let the tracker know that the sync state may have changed */
- public synchronized void update() {
- final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty();
- if (isSyncInProgress == mLastWasSyncing) return;
- final long now = SystemClock.elapsedRealtime();
- if (isSyncInProgress) {
- mWhenSyncStarted = now;
- } else {
- mTimeSpentSyncing += now - mWhenSyncStarted;
- }
- mLastWasSyncing = isSyncInProgress;
- }
-
- /** Get how long we have been syncing, in ms */
- public synchronized long timeSpentSyncing() {
- if (!mLastWasSyncing) return mTimeSpentSyncing;
-
- final long now = SystemClock.elapsedRealtime();
- return mTimeSpentSyncing + (now - mWhenSyncStarted);
- }
- }
-
- class ServiceConnectionData {
- public final ActiveSyncContext activeSyncContext;
- public final ISyncAdapter syncAdapter;
- ServiceConnectionData(ActiveSyncContext activeSyncContext, ISyncAdapter syncAdapter) {
- this.activeSyncContext = activeSyncContext;
- this.syncAdapter = syncAdapter;
- }
- }
-
- /**
- * Handles SyncOperation Messages that are posted to the associated
- * HandlerThread.
- */
- class SyncHandler extends Handler {
- // Messages that can be sent on mHandler
- private static final int MESSAGE_SYNC_FINISHED = 1;
- private static final int MESSAGE_SYNC_ALARM = 2;
- private static final int MESSAGE_CHECK_ALARMS = 3;
- private static final int MESSAGE_SERVICE_CONNECTED = 4;
- private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
- private static final int MESSAGE_CANCEL = 6;
-
- public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
- private Long mAlarmScheduleTime = null;
- public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
- private final HashMap<Pair<Account, String>, PowerManager.WakeLock> mWakeLocks =
- Maps.newHashMap();
-
- private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
-
- public void onBootCompleted() {
- mBootCompleted = true;
-
- doDatabaseCleanup();
-
- if (mReadyToRunLatch != null) {
- mReadyToRunLatch.countDown();
- }
- }
-
- private PowerManager.WakeLock getSyncWakeLock(Account account, String authority) {
- final Pair<Account, String> wakeLockKey = Pair.create(account, authority);
- PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey);
- if (wakeLock == null) {
- final String name = SYNC_WAKE_LOCK_PREFIX + "_" + authority + "_" + account;
- wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
- wakeLock.setReferenceCounted(false);
- mWakeLocks.put(wakeLockKey, wakeLock);
- }
- return wakeLock;
- }
-
- private void waitUntilReadyToRun() {
- CountDownLatch latch = mReadyToRunLatch;
- if (latch != null) {
- while (true) {
- try {
- latch.await();
- mReadyToRunLatch = null;
- return;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- }
- }
- /**
- * Used to keep track of whether a sync notification is active and who it is for.
- */
- class SyncNotificationInfo {
- // true iff the notification manager has been asked to send the notification
- public boolean isActive = false;
-
- // Set when we transition from not running a sync to running a sync, and cleared on
- // the opposite transition.
- public Long startTime = null;
-
- public void toString(StringBuilder sb) {
- sb.append("isActive ").append(isActive).append(", startTime ").append(startTime);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- toString(sb);
- return sb.toString();
- }
- }
-
- public SyncHandler(Looper looper) {
- super(looper);
- }
-
- public void handleMessage(Message msg) {
- long earliestFuturePollTime = Long.MAX_VALUE;
- long nextPendingSyncTime = Long.MAX_VALUE;
-
- // Setting the value here instead of a method because we want the dumpsys logs
- // to have the most recent value used.
- try {
- waitUntilReadyToRun();
- mDataConnectionIsConnected = readDataConnectionState();
- mSyncManagerWakeLock.acquire();
- // Always do this first so that we be sure that any periodic syncs that
- // are ready to run have been converted into pending syncs. This allows the
- // logic that considers the next steps to take based on the set of pending syncs
- // to also take into account the periodic syncs.
- earliestFuturePollTime = scheduleReadyPeriodicSyncs();
- switch (msg.what) {
- case SyncHandler.MESSAGE_CANCEL: {
- Pair<Account, String> payload = (Pair<Account, String>)msg.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
- + payload.first + ", " + payload.second);
- }
- cancelActiveSyncLocked(payload.first, msg.arg1, payload.second);
- nextPendingSyncTime = maybeStartNextSyncLocked();
- break;
- }
-
- case SyncHandler.MESSAGE_SYNC_FINISHED:
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
- }
- SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
- if (!isSyncStillActive(payload.activeSyncContext)) {
- Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
- + "sync is no longer active: "
- + payload.activeSyncContext);
- break;
- }
- runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext);
-
- // since a sync just finished check if it is time to start a new sync
- nextPendingSyncTime = maybeStartNextSyncLocked();
- break;
-
- case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
- ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
- + msgData.activeSyncContext);
- }
- // check that this isn't an old message
- if (isSyncStillActive(msgData.activeSyncContext)) {
- runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter);
- }
- break;
- }
-
- case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
- final ActiveSyncContext currentSyncContext =
- ((ServiceConnectionData)msg.obj).activeSyncContext;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
- + currentSyncContext);
- }
- // check that this isn't an old message
- if (isSyncStillActive(currentSyncContext)) {
- // cancel the sync if we have a syncadapter, which means one is
- // outstanding
- if (currentSyncContext.mSyncAdapter != null) {
- try {
- currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext);
- } catch (RemoteException e) {
- // we don't need to retry this in this case
- }
- }
-
- // pretend that the sync failed with an IOException,
- // which is a soft error
- SyncResult syncResult = new SyncResult();
- syncResult.stats.numIoExceptions++;
- runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext);
-
- // since a sync just finished check if it is time to start a new sync
- nextPendingSyncTime = maybeStartNextSyncLocked();
- }
-
- break;
- }
-
- case SyncHandler.MESSAGE_SYNC_ALARM: {
- boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- if (isLoggable) {
- Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_ALARM");
- }
- mAlarmScheduleTime = null;
- try {
- nextPendingSyncTime = maybeStartNextSyncLocked();
- } finally {
- mHandleAlarmWakeLock.release();
- }
- break;
- }
-
- case SyncHandler.MESSAGE_CHECK_ALARMS:
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
- }
- nextPendingSyncTime = maybeStartNextSyncLocked();
- break;
- }
- } finally {
- manageSyncNotificationLocked();
- manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime);
- mSyncTimeTracker.update();
- mSyncManagerWakeLock.release();
- }
- }
-
- /**
- * Turn any periodic sync operations that are ready to run into pending sync operations.
- * @return the desired start time of the earliest future periodic sync operation,
- * in milliseconds since boot
- */
- private long scheduleReadyPeriodicSyncs() {
- final boolean backgroundDataUsageAllowed =
- getConnectivityManager().getBackgroundDataSetting();
- long earliestFuturePollTime = Long.MAX_VALUE;
- if (!backgroundDataUsageAllowed) {
- return earliestFuturePollTime;
- }
-
- AccountAndUser[] accounts = mRunningAccounts;
-
- final long nowAbsolute = System.currentTimeMillis();
- final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis)
- ? (nowAbsolute - mSyncRandomOffsetMillis) : 0;
-
- ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
- for (SyncStorageEngine.AuthorityInfo info : infos) {
- // skip the sync if the account of this operation no longer exists
- if (!containsAccountAndUser(accounts, info.account, info.userId)) {
- continue;
- }
-
- if (!mSyncStorageEngine.getMasterSyncAutomatically(info.userId)
- || !mSyncStorageEngine.getSyncAutomatically(info.account, info.userId,
- info.authority)) {
- continue;
- }
-
- if (mSyncStorageEngine.getIsSyncable(info.account, info.userId, info.authority)
- == 0) {
- continue;
- }
-
- SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(info);
- for (int i = 0, N = info.periodicSyncs.size(); i < N; i++) {
- final Bundle extras = info.periodicSyncs.get(i).first;
- final Long periodInMillis = info.periodicSyncs.get(i).second * 1000;
- // find when this periodic sync was last scheduled to run
- final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
-
- long remainingMillis
- = periodInMillis - (shiftedNowAbsolute % periodInMillis);
-
- /*
- * Sync scheduling strategy:
- * Set the next periodic sync based on a random offset (in seconds).
- *
- * Also sync right now if any of the following cases hold
- * and mark it as having been scheduled
- *
- * Case 1: This sync is ready to run now.
- * Case 2: If the lastPollTimeAbsolute is in the future,
- * sync now and reinitialize. This can happen for
- * example if the user changed the time, synced and
- * changed back.
- * Case 3: If we failed to sync at the last scheduled time
- */
- if (remainingMillis == periodInMillis // Case 1
- || lastPollTimeAbsolute > nowAbsolute // Case 2
- || (nowAbsolute - lastPollTimeAbsolute
- >= periodInMillis)) { // Case 3
- // Sync now
- final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
- info.account, info.userId, info.authority);
- final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
- syncAdapterInfo = mSyncAdapters.getServiceInfo(
- SyncAdapterType.newKey(info.authority, info.account.type),
- info.userId);
- if (syncAdapterInfo == null) {
- continue;
- }
- scheduleSyncOperation(
- new SyncOperation(info.account, info.userId,
- SyncStorageEngine.SOURCE_PERIODIC,
- info.authority, extras, 0 /* delay */,
- backoff != null ? backoff.first : 0,
- mSyncStorageEngine.getDelayUntilTime(
- info.account, info.userId, info.authority),
- syncAdapterInfo.type.allowParallelSyncs()));
- status.setPeriodicSyncTime(i, nowAbsolute);
- }
- // Compute when this periodic sync should next run
- final long nextPollTimeAbsolute = nowAbsolute + remainingMillis;
-
- // remember this time if it is earlier than earliestFuturePollTime
- if (nextPollTimeAbsolute < earliestFuturePollTime) {
- earliestFuturePollTime = nextPollTimeAbsolute;
- }
- }
- }
-
- if (earliestFuturePollTime == Long.MAX_VALUE) {
- return Long.MAX_VALUE;
- }
-
- // convert absolute time to elapsed time
- return SystemClock.elapsedRealtime()
- + ((earliestFuturePollTime < nowAbsolute)
- ? 0
- : (earliestFuturePollTime - nowAbsolute));
- }
-
- private long maybeStartNextSyncLocked() {
- final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- if (isLoggable) Log.v(TAG, "maybeStartNextSync");
-
- // If we aren't ready to run (e.g. the data connection is down), get out.
- if (!mDataConnectionIsConnected) {
- if (isLoggable) {
- Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
- }
- return Long.MAX_VALUE;
- }
-
- if (mStorageIsLow) {
- if (isLoggable) {
- Log.v(TAG, "maybeStartNextSync: memory low, skipping");
- }
- return Long.MAX_VALUE;
- }
-
- // If the accounts aren't known yet then we aren't ready to run. We will be kicked
- // when the account lookup request does complete.
- AccountAndUser[] accounts = mRunningAccounts;
- if (accounts == INITIAL_ACCOUNTS_ARRAY) {
- if (isLoggable) {
- Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
- }
- return Long.MAX_VALUE;
- }
-
- // Otherwise consume SyncOperations from the head of the SyncQueue until one is
- // found that is runnable (not disabled, etc). If that one is ready to run then
- // start it, otherwise just get out.
- final boolean backgroundDataUsageAllowed =
- getConnectivityManager().getBackgroundDataSetting();
-
- final long now = SystemClock.elapsedRealtime();
-
- // will be set to the next time that a sync should be considered for running
- long nextReadyToRunTime = Long.MAX_VALUE;
-
- // order the sync queue, dropping syncs that are not allowed
- ArrayList<SyncOperation> operations = new ArrayList<SyncOperation>();
- synchronized (mSyncQueue) {
- if (isLoggable) {
- Log.v(TAG, "build the operation array, syncQueue size is "
- + mSyncQueue.getOperations().size());
- }
- final Iterator<SyncOperation> operationIterator = mSyncQueue.getOperations()
- .iterator();
-
- final ActivityManager activityManager
- = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
- final Set<Integer> removedUsers = Sets.newHashSet();
- while (operationIterator.hasNext()) {
- final SyncOperation op = operationIterator.next();
-
- // drop the sync if the account of this operation no longer exists
- if (!containsAccountAndUser(accounts, op.account, op.userId)) {
- operationIterator.remove();
- mSyncStorageEngine.deleteFromPending(op.pendingOperation);
- continue;
- }
-
- // drop this sync request if it isn't syncable
- int syncableState = mSyncStorageEngine.getIsSyncable(
- op.account, op.userId, op.authority);
- if (syncableState == 0) {
- operationIterator.remove();
- mSyncStorageEngine.deleteFromPending(op.pendingOperation);
- 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) {
- if (nextReadyToRunTime > op.effectiveRunTime) {
- nextReadyToRunTime = op.effectiveRunTime;
- }
- continue;
- }
-
- final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
- syncAdapterInfo = mSyncAdapters.getServiceInfo(
- SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
-
- // only proceed if network is connected for requesting UID
- final boolean uidNetworkConnected;
- if (syncAdapterInfo != null) {
- final NetworkInfo networkInfo = getConnectivityManager()
- .getActiveNetworkInfoForUid(syncAdapterInfo.uid);
- uidNetworkConnected = networkInfo != null && networkInfo.isConnected();
- } else {
- uidNetworkConnected = false;
- }
-
- // skip the sync if it isn't manual, and auto sync or
- // background data usage is disabled or network is
- // disconnected for the target UID.
- if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
- && (syncableState > 0)
- && (!mSyncStorageEngine.getMasterSyncAutomatically(op.userId)
- || !backgroundDataUsageAllowed
- || !uidNetworkConnected
- || !mSyncStorageEngine.getSyncAutomatically(
- op.account, op.userId, op.authority))) {
- operationIterator.remove();
- mSyncStorageEngine.deleteFromPending(op.pendingOperation);
- continue;
- }
-
- operations.add(op);
- }
- for (Integer user : removedUsers) {
- // if it's still removed
- if (mUserManager.getUserInfo(user) == null) {
- onUserRemoved(user);
- }
- }
- }
-
- // find the next operation to dispatch, if one is ready
- // iterate from the top, keep issuing (while potentially cancelling existing syncs)
- // until the quotas are filled.
- // once the quotas are filled iterate once more to find when the next one would be
- // (also considering pre-emption reasons).
- if (isLoggable) Log.v(TAG, "sort the candidate operations, size " + operations.size());
- Collections.sort(operations);
- if (isLoggable) Log.v(TAG, "dispatch all ready sync operations");
- for (int i = 0, N = operations.size(); i < N; i++) {
- final SyncOperation candidate = operations.get(i);
- final boolean candidateIsInitialization = candidate.isInitialization();
-
- int numInit = 0;
- int numRegular = 0;
- ActiveSyncContext conflict = null;
- ActiveSyncContext longRunning = null;
- ActiveSyncContext toReschedule = null;
- ActiveSyncContext oldestNonExpeditedRegular = null;
-
- for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
- final SyncOperation activeOp = activeSyncContext.mSyncOperation;
- if (activeOp.isInitialization()) {
- numInit++;
- } else {
- numRegular++;
- if (!activeOp.isExpedited()) {
- if (oldestNonExpeditedRegular == null
- || (oldestNonExpeditedRegular.mStartTime
- > activeSyncContext.mStartTime)) {
- oldestNonExpeditedRegular = activeSyncContext;
- }
- }
- }
- if (activeOp.account.type.equals(candidate.account.type)
- && activeOp.authority.equals(candidate.authority)
- && activeOp.userId == candidate.userId
- && (!activeOp.allowParallelSyncs
- || activeOp.account.name.equals(candidate.account.name))) {
- conflict = activeSyncContext;
- // don't break out since we want to do a full count of the varieties
- } else {
- if (candidateIsInitialization == activeOp.isInitialization()
- && activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) {
- longRunning = activeSyncContext;
- // don't break out since we want to do a full count of the varieties
- }
- }
- }
-
- if (isLoggable) {
- Log.v(TAG, "candidate " + (i + 1) + " of " + N + ": " + candidate);
- Log.v(TAG, " numActiveInit=" + numInit + ", numActiveRegular=" + numRegular);
- Log.v(TAG, " longRunning: " + longRunning);
- Log.v(TAG, " conflict: " + conflict);
- Log.v(TAG, " oldestNonExpeditedRegular: " + oldestNonExpeditedRegular);
- }
-
- final boolean roomAvailable = candidateIsInitialization
- ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS
- : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS;
-
- if (conflict != null) {
- if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization()
- && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) {
- toReschedule = conflict;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "canceling and rescheduling sync since an initialization "
- + "takes higher priority, " + conflict);
- }
- } else if (candidate.expedited && !conflict.mSyncOperation.expedited
- && (candidateIsInitialization
- == conflict.mSyncOperation.isInitialization())) {
- toReschedule = conflict;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "canceling and rescheduling sync since an expedited "
- + "takes higher priority, " + conflict);
- }
- } else {
- continue;
- }
- } else if (roomAvailable) {
- // dispatch candidate
- } else if (candidate.isExpedited() && oldestNonExpeditedRegular != null
- && !candidateIsInitialization) {
- // We found an active, non-expedited regular sync. We also know that the
- // candidate doesn't conflict with this active sync since conflict
- // is null. Reschedule the active sync and start the candidate.
- toReschedule = oldestNonExpeditedRegular;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "canceling and rescheduling sync since an expedited is ready to run, "
- + oldestNonExpeditedRegular);
- }
- } else if (longRunning != null
- && (candidateIsInitialization
- == longRunning.mSyncOperation.isInitialization())) {
- // We found an active, long-running sync. Reschedule the active
- // sync and start the candidate.
- toReschedule = longRunning;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "canceling and rescheduling sync since it ran roo long, "
- + longRunning);
- }
- } else {
- // we were unable to find or make space to run this candidate, go on to
- // the next one
- continue;
- }
-
- if (toReschedule != null) {
- runSyncFinishedOrCanceledLocked(null, toReschedule);
- scheduleSyncOperation(toReschedule.mSyncOperation);
- }
- synchronized (mSyncQueue) {
- mSyncQueue.remove(candidate);
- }
- dispatchSyncOperation(candidate);
- }
-
- return nextReadyToRunTime;
- }
-
- private boolean dispatchSyncOperation(SyncOperation op) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "dispatchSyncOperation: we are going to sync " + op);
- Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size());
- for (ActiveSyncContext syncContext : mActiveSyncContexts) {
- Log.v(TAG, syncContext.toString());
- }
- }
-
- // connect to the sync adapter
- SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type);
- final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
- syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, op.userId);
- if (syncAdapterInfo == null) {
- Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
- + ", removing settings for it");
- mSyncStorageEngine.removeAuthority(op.account, op.userId, op.authority);
- return false;
- }
-
- ActiveSyncContext activeSyncContext =
- new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid);
- activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext);
- mActiveSyncContexts.add(activeSyncContext);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext);
- }
- if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo, op.userId)) {
- Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
- closeActiveSyncContext(activeSyncContext);
- return false;
- }
-
- return true;
- }
-
- private void runBoundToSyncAdapter(final ActiveSyncContext activeSyncContext,
- ISyncAdapter syncAdapter) {
- activeSyncContext.mSyncAdapter = syncAdapter;
- final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
- try {
- activeSyncContext.mIsLinkedToDeath = true;
- syncAdapter.asBinder().linkToDeath(activeSyncContext, 0);
-
- syncAdapter.startSync(activeSyncContext, syncOperation.authority,
- syncOperation.account, syncOperation.extras);
- } catch (RemoteException remoteExc) {
- Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc);
- closeActiveSyncContext(activeSyncContext);
- increaseBackoffSetting(syncOperation);
- scheduleSyncOperation(new SyncOperation(syncOperation));
- } catch (RuntimeException exc) {
- closeActiveSyncContext(activeSyncContext);
- Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
- }
- }
-
- private void cancelActiveSyncLocked(Account account, int userId, String authority) {
- ArrayList<ActiveSyncContext> activeSyncs =
- new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
- for (ActiveSyncContext activeSyncContext : activeSyncs) {
- if (activeSyncContext != null) {
- // 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 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 != UserHandle.USER_ALL
- && userId != activeSyncContext.mSyncOperation.userId) {
- continue;
- }
- runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */,
- activeSyncContext);
- }
- }
- }
-
- private void runSyncFinishedOrCanceledLocked(SyncResult syncResult,
- ActiveSyncContext activeSyncContext) {
- boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
-
- if (activeSyncContext.mIsLinkedToDeath) {
- activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0);
- activeSyncContext.mIsLinkedToDeath = false;
- }
- closeActiveSyncContext(activeSyncContext);
-
- final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
-
- final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime;
-
- String historyMessage;
- int downstreamActivity;
- int upstreamActivity;
- if (syncResult != null) {
- if (isLoggable) {
- Log.v(TAG, "runSyncFinishedOrCanceled [finished]: "
- + syncOperation + ", result " + syncResult);
- }
-
- if (!syncResult.hasError()) {
- historyMessage = SyncStorageEngine.MESG_SUCCESS;
- // TODO: set these correctly when the SyncResult is extended to include it
- downstreamActivity = 0;
- upstreamActivity = 0;
- clearBackoffSetting(syncOperation);
- } else {
- Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
- // the operation failed so increase the backoff time
- if (!syncResult.syncAlreadyInProgress) {
- increaseBackoffSetting(syncOperation);
- }
- // reschedule the sync if so indicated by the syncResult
- maybeRescheduleSync(syncResult, syncOperation);
- historyMessage = Integer.toString(syncResultToErrorNumber(syncResult));
- // TODO: set these correctly when the SyncResult is extended to include it
- downstreamActivity = 0;
- upstreamActivity = 0;
- }
-
- setDelayUntilTime(syncOperation, syncResult.delayUntil);
- } else {
- if (isLoggable) {
- Log.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation);
- }
- if (activeSyncContext.mSyncAdapter != null) {
- try {
- activeSyncContext.mSyncAdapter.cancelSync(activeSyncContext);
- } catch (RemoteException e) {
- // we don't need to retry this in this case
- }
- }
- historyMessage = SyncStorageEngine.MESG_CANCELED;
- downstreamActivity = 0;
- upstreamActivity = 0;
- }
-
- stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
- upstreamActivity, downstreamActivity, elapsedTime);
-
- if (syncResult != null && syncResult.tooManyDeletions) {
- installHandleTooManyDeletesNotification(syncOperation.account,
- syncOperation.authority, syncResult.stats.numDeletes,
- syncOperation.userId);
- } else {
- mNotificationMgr.cancelAsUser(null,
- syncOperation.account.hashCode() ^ syncOperation.authority.hashCode(),
- new UserHandle(syncOperation.userId));
- }
-
- if (syncResult != null && syncResult.fullSyncRequested) {
- scheduleSyncOperation(new SyncOperation(syncOperation.account, syncOperation.userId,
- syncOperation.syncSource, syncOperation.authority, new Bundle(), 0,
- syncOperation.backoff, syncOperation.delayUntil,
- syncOperation.allowParallelSyncs));
- }
- // no need to schedule an alarm, as that will be done by our caller.
- }
-
- private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) {
- activeSyncContext.close();
- mActiveSyncContexts.remove(activeSyncContext);
- mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo,
- activeSyncContext.mSyncOperation.userId);
- }
-
- /**
- * Convert the error-containing SyncResult into the Sync.History error number. Since
- * the SyncResult may indicate multiple errors at once, this method just returns the
- * most "serious" error.
- * @param syncResult the SyncResult from which to read
- * @return the most "serious" error set in the SyncResult
- * @throws IllegalStateException if the SyncResult does not indicate any errors.
- * If SyncResult.error() is true then it is safe to call this.
- */
- private int syncResultToErrorNumber(SyncResult syncResult) {
- if (syncResult.syncAlreadyInProgress)
- return ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
- if (syncResult.stats.numAuthExceptions > 0)
- return ContentResolver.SYNC_ERROR_AUTHENTICATION;
- if (syncResult.stats.numIoExceptions > 0)
- return ContentResolver.SYNC_ERROR_IO;
- if (syncResult.stats.numParseExceptions > 0)
- return ContentResolver.SYNC_ERROR_PARSE;
- if (syncResult.stats.numConflictDetectedExceptions > 0)
- return ContentResolver.SYNC_ERROR_CONFLICT;
- if (syncResult.tooManyDeletions)
- return ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS;
- if (syncResult.tooManyRetries)
- return ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES;
- if (syncResult.databaseError)
- return ContentResolver.SYNC_ERROR_INTERNAL;
- throw new IllegalStateException("we are not in an error state, " + syncResult);
- }
-
- private void manageSyncNotificationLocked() {
- boolean shouldCancel;
- boolean shouldInstall;
-
- if (mActiveSyncContexts.isEmpty()) {
- mSyncNotificationInfo.startTime = null;
-
- // we aren't syncing. if the notification is active then remember that we need
- // to cancel it and then clear out the info
- shouldCancel = mSyncNotificationInfo.isActive;
- shouldInstall = false;
- } else {
- // we are syncing
- final long now = SystemClock.elapsedRealtime();
- if (mSyncNotificationInfo.startTime == null) {
- mSyncNotificationInfo.startTime = now;
- }
-
- // there are three cases:
- // - the notification is up: do nothing
- // - the notification is not up but it isn't time yet: don't install
- // - the notification is not up and it is time: need to install
-
- if (mSyncNotificationInfo.isActive) {
- shouldInstall = shouldCancel = false;
- } else {
- // it isn't currently up, so there is nothing to cancel
- shouldCancel = false;
-
- final boolean timeToShowNotification =
- now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
- if (timeToShowNotification) {
- shouldInstall = true;
- } else {
- // show the notification immediately if this is a manual sync
- shouldInstall = false;
- for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
- final boolean manualSync = activeSyncContext.mSyncOperation.extras
- .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
- if (manualSync) {
- shouldInstall = true;
- break;
- }
- }
- }
- }
- }
-
- if (shouldCancel && !shouldInstall) {
- mNeedSyncActiveNotification = false;
- sendSyncStateIntent();
- mSyncNotificationInfo.isActive = false;
- }
-
- if (shouldInstall) {
- mNeedSyncActiveNotification = true;
- sendSyncStateIntent();
- mSyncNotificationInfo.isActive = true;
- }
- }
-
- private void manageSyncAlarmLocked(long nextPeriodicEventElapsedTime,
- long nextPendingEventElapsedTime) {
- // in each of these cases the sync loop will be kicked, which will cause this
- // method to be called again
- if (!mDataConnectionIsConnected) return;
- if (mStorageIsLow) return;
-
- // When the status bar notification should be raised
- final long notificationTime =
- (!mSyncHandler.mSyncNotificationInfo.isActive
- && mSyncHandler.mSyncNotificationInfo.startTime != null)
- ? mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY
- : Long.MAX_VALUE;
-
- // When we should consider canceling an active sync
- long earliestTimeoutTime = Long.MAX_VALUE;
- for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
- final long currentSyncTimeoutTime =
- currentSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: active sync, mTimeoutStartTime + MAX is "
- + currentSyncTimeoutTime);
- }
- if (earliestTimeoutTime > currentSyncTimeoutTime) {
- earliestTimeoutTime = currentSyncTimeoutTime;
- }
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: notificationTime is " + notificationTime);
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: earliestTimeoutTime is " + earliestTimeoutTime);
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: nextPeriodicEventElapsedTime is "
- + nextPeriodicEventElapsedTime);
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: nextPendingEventElapsedTime is "
- + nextPendingEventElapsedTime);
- }
-
- long alarmTime = Math.min(notificationTime, earliestTimeoutTime);
- alarmTime = Math.min(alarmTime, nextPeriodicEventElapsedTime);
- alarmTime = Math.min(alarmTime, nextPendingEventElapsedTime);
-
- // Bound the alarm time.
- final long now = SystemClock.elapsedRealtime();
- if (alarmTime < now + SYNC_ALARM_TIMEOUT_MIN) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: the alarmTime is too small, "
- + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
- }
- alarmTime = now + SYNC_ALARM_TIMEOUT_MIN;
- } else if (alarmTime > now + SYNC_ALARM_TIMEOUT_MAX) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: the alarmTime is too large, "
- + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
- }
- alarmTime = now + SYNC_ALARM_TIMEOUT_MAX;
- }
-
- // determine if we need to set or cancel the alarm
- boolean shouldSet = false;
- boolean shouldCancel = false;
- final boolean alarmIsActive = mAlarmScheduleTime != null;
- final boolean needAlarm = alarmTime != Long.MAX_VALUE;
- if (needAlarm) {
- if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
- shouldSet = true;
- }
- } else {
- shouldCancel = alarmIsActive;
- }
-
- // set or cancel the alarm as directed
- ensureAlarmService();
- if (shouldSet) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "requesting that the alarm manager wake us up at elapsed time "
- + alarmTime + ", now is " + now + ", " + ((alarmTime - now) / 1000)
- + " secs from now");
- }
- mAlarmScheduleTime = alarmTime;
- mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
- mSyncAlarmIntent);
- } else if (shouldCancel) {
- mAlarmScheduleTime = null;
- mAlarmService.cancel(mSyncAlarmIntent);
- }
- }
-
- private void sendSyncStateIntent() {
- Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
- syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
- syncStateIntent.putExtra("failing", false);
- mContext.sendBroadcastAsUser(syncStateIntent, UserHandle.OWNER);
- }
-
- private void installHandleTooManyDeletesNotification(Account account, String authority,
- long numDeletes, int userId) {
- if (mNotificationMgr == null) return;
-
- final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider(
- authority, 0 /* flags */);
- if (providerInfo == null) {
- return;
- }
- CharSequence authorityName = providerInfo.loadLabel(mContext.getPackageManager());
-
- Intent clickIntent = new Intent(mContext, SyncActivityTooManyDeletes.class);
- clickIntent.putExtra("account", account);
- clickIntent.putExtra("authority", authority);
- clickIntent.putExtra("provider", authorityName.toString());
- clickIntent.putExtra("numDeletes", numDeletes);
-
- if (!isActivityAvailable(clickIntent)) {
- Log.w(TAG, "No activity found to handle too many deletes.");
- return;
- }
-
- final PendingIntent pendingIntent = PendingIntent
- .getActivityAsUser(mContext, 0, clickIntent,
- PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(userId));
-
- CharSequence tooManyDeletesDescFormat = mContext.getResources().getText(
- R.string.contentServiceTooManyDeletesNotificationDesc);
-
- Notification notification =
- new Notification(R.drawable.stat_notify_sync_error,
- mContext.getString(R.string.contentServiceSync),
- System.currentTimeMillis());
- notification.setLatestEventInfo(mContext,
- mContext.getString(R.string.contentServiceSyncNotificationTitle),
- String.format(tooManyDeletesDescFormat.toString(), authorityName),
- pendingIntent);
- notification.flags |= Notification.FLAG_ONGOING_EVENT;
- mNotificationMgr.notifyAsUser(null, account.hashCode() ^ authority.hashCode(),
- notification, new UserHandle(userId));
- }
-
- /**
- * Checks whether an activity exists on the system image for the given intent.
- *
- * @param intent The intent for an activity.
- * @return Whether or not an activity exists.
- */
- private boolean isActivityAvailable(Intent intent) {
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
- int listSize = list.size();
- for (int i = 0; i < listSize; i++) {
- ResolveInfo resolveInfo = list.get(i);
- if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
- != 0) {
- return true;
- }
- }
-
- return false;
- }
-
- public long insertStartSyncEvent(SyncOperation syncOperation) {
- final int source = syncOperation.syncSource;
- final long now = System.currentTimeMillis();
-
- EventLog.writeEvent(2720, syncOperation.authority,
- SyncStorageEngine.EVENT_START, source,
- syncOperation.account.name.hashCode());
-
- return mSyncStorageEngine.insertStartSyncEvent(
- syncOperation.account, syncOperation.userId, syncOperation.authority,
- now, source, syncOperation.isInitialization());
- }
-
- public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
- int upstreamActivity, int downstreamActivity, long elapsedTime) {
- EventLog.writeEvent(2720, syncOperation.authority,
- SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
- syncOperation.account.name.hashCode());
-
- mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime,
- resultMessage, downstreamActivity, upstreamActivity);
- }
- }
-
- private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) {
- for (ActiveSyncContext sync : mActiveSyncContexts) {
- if (sync == activeSyncContext) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
deleted file mode 100644
index 6611fcd..0000000
--- a/core/java/android/content/SyncOperation.java
+++ /dev/null
@@ -1,171 +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.content;
-
-import android.accounts.Account;
-import android.os.Bundle;
-import android.os.SystemClock;
-
-/**
- * Value type that represents a sync operation.
- * @hide
- */
-public class SyncOperation implements Comparable {
- public final Account account;
- public final int userId;
- public int syncSource;
- public String authority;
- public final boolean allowParallelSyncs;
- public Bundle extras;
- public final String key;
- public long earliestRunTime;
- public boolean expedited;
- public SyncStorageEngine.PendingOperation pendingOperation;
- public Long backoff;
- public long delayUntil;
- public long effectiveRunTime;
-
- public SyncOperation(Account account, int userId, int source, String authority, Bundle extras,
- long delayInMs, long backoff, long delayUntil, boolean allowParallelSyncs) {
- this.account = account;
- this.userId = userId;
- this.syncSource = source;
- this.authority = authority;
- this.allowParallelSyncs = allowParallelSyncs;
- this.extras = new Bundle(extras);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_UPLOAD);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_MANUAL);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
- this.delayUntil = delayUntil;
- this.backoff = backoff;
- final long now = SystemClock.elapsedRealtime();
- if (delayInMs < 0) {
- this.expedited = true;
- this.earliestRunTime = now;
- } else {
- this.expedited = false;
- this.earliestRunTime = now + delayInMs;
- }
- updateEffectiveRunTime();
- this.key = toKey();
- }
-
- private void removeFalseExtra(String extraName) {
- if (!extras.getBoolean(extraName, false)) {
- extras.remove(extraName);
- }
- }
-
- SyncOperation(SyncOperation other) {
- this.account = other.account;
- this.userId = other.userId;
- this.syncSource = other.syncSource;
- this.authority = other.authority;
- this.extras = new Bundle(other.extras);
- this.expedited = other.expedited;
- this.earliestRunTime = SystemClock.elapsedRealtime();
- this.backoff = other.backoff;
- this.delayUntil = other.delayUntil;
- this.allowParallelSyncs = other.allowParallelSyncs;
- this.updateEffectiveRunTime();
- this.key = toKey();
- }
-
- public String toString() {
- return dump(true);
- }
-
- public String dump(boolean useOneLine) {
- 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");
- }
- if (!useOneLine && !extras.keySet().isEmpty()) {
- sb.append("\n ");
- extrasToStringBuilder(extras, sb);
- }
- return sb.toString();
- }
-
- public boolean isInitialization() {
- return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
- }
-
- public boolean isExpedited() {
- return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
- }
-
- public boolean ignoreBackoff() {
- return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
- }
-
- private String toKey() {
- StringBuilder sb = new StringBuilder();
- sb.append("authority: ").append(authority);
- sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type
- + "}");
- sb.append(" extras: ");
- extrasToStringBuilder(extras, sb);
- return sb.toString();
- }
-
- public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
- sb.append("[");
- for (String key : bundle.keySet()) {
- sb.append(key).append("=").append(bundle.get(key)).append(" ");
- }
- sb.append("]");
- }
-
- public void updateEffectiveRunTime() {
- effectiveRunTime = ignoreBackoff()
- ? earliestRunTime
- : Math.max(
- Math.max(earliestRunTime, delayUntil),
- backoff);
- }
-
- public int compareTo(Object o) {
- SyncOperation other = (SyncOperation)o;
-
- if (expedited != other.expedited) {
- return expedited ? -1 : 1;
- }
-
- if (effectiveRunTime == other.effectiveRunTime) {
- return 0;
- }
-
- return effectiveRunTime < other.effectiveRunTime ? -1 : 1;
- }
-}
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
deleted file mode 100644
index c9a325e..0000000
--- a/core/java/android/content/SyncQueue.java
+++ /dev/null
@@ -1,220 +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.content;
-
-import android.accounts.Account;
-import android.content.pm.RegisteredServicesCache.ServiceInfo;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.google.android.collect.Maps;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Queue of pending sync operations. Not inherently thread safe, external
- * callers are responsible for locking.
- *
- * @hide
- */
-public class SyncQueue {
- private static final String TAG = "SyncManager";
-
- private final SyncStorageEngine mSyncStorageEngine;
- private final SyncAdaptersCache mSyncAdapters;
-
- // A Map of SyncOperations operationKey -> SyncOperation that is designed for
- // quick lookup of an enqueued SyncOperation.
- private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
-
- public SyncQueue(SyncStorageEngine syncStorageEngine, final SyncAdaptersCache syncAdapters) {
- mSyncStorageEngine = syncStorageEngine;
- mSyncAdapters = syncAdapters;
- }
-
- public void addPendingOperations(int userId) {
- for (SyncStorageEngine.PendingOperation op : mSyncStorageEngine.getPendingOperations()) {
- if (op.userId != userId) continue;
-
- final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
- op.account, op.userId, op.authority);
- final ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo(
- SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
- if (syncAdapterInfo == null) {
- Log.w(TAG, "Missing sync adapter info for authority " + op.authority + ", userId "
- + op.userId);
- continue;
- }
- SyncOperation syncOperation = new SyncOperation(
- op.account, op.userId, op.syncSource, op.authority, op.extras, 0 /* delay */,
- backoff != null ? backoff.first : 0,
- mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority),
- syncAdapterInfo.type.allowParallelSyncs());
- syncOperation.expedited = op.expedited;
- syncOperation.pendingOperation = op;
- add(syncOperation, op);
- }
- }
-
- public boolean add(SyncOperation operation) {
- return add(operation, null /* this is not coming from the database */);
- }
-
- private boolean add(SyncOperation operation,
- SyncStorageEngine.PendingOperation pop) {
- // - if an operation with the same key exists and this one should run earlier,
- // update the earliestRunTime of the existing to the new time
- // - if an operation with the same key exists and if this one should run
- // later, ignore it
- // - if no operation exists then add the new one
- final String operationKey = operation.key;
- final SyncOperation existingOperation = mOperationsMap.get(operationKey);
-
- if (existingOperation != null) {
- boolean changed = false;
- if (existingOperation.expedited == operation.expedited) {
- final long newRunTime =
- Math.min(existingOperation.earliestRunTime, operation.earliestRunTime);
- if (existingOperation.earliestRunTime != newRunTime) {
- existingOperation.earliestRunTime = newRunTime;
- changed = true;
- }
- } else {
- if (operation.expedited) {
- existingOperation.expedited = true;
- changed = true;
- }
- }
- return changed;
- }
-
- operation.pendingOperation = pop;
- if (operation.pendingOperation == null) {
- pop = new SyncStorageEngine.PendingOperation(
- operation.account, operation.userId, operation.syncSource,
- operation.authority, operation.extras, operation.expedited);
- pop = mSyncStorageEngine.insertIntoPending(pop);
- if (pop == null) {
- throw new IllegalStateException("error adding pending sync operation "
- + operation);
- }
- operation.pendingOperation = pop;
- }
-
- mOperationsMap.put(operationKey, operation);
- return true;
- }
-
- public void removeUser(int userId) {
- ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>();
- for (SyncOperation op : mOperationsMap.values()) {
- if (op.userId == userId) {
- opsToRemove.add(op);
- }
- }
-
- for (SyncOperation op : opsToRemove) {
- remove(op);
- }
- }
-
- /**
- * Remove the specified operation if it is in the queue.
- * @param operation the operation to remove
- */
- public void remove(SyncOperation operation) {
- SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
- if (operationToRemove == null) {
- return;
- }
- if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
- final String errorMessage = "unable to find pending row for " + operationToRemove;
- Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
- }
- }
-
- public void onBackoffChanged(Account account, int userId, String providerName, long backoff) {
- // for each op that matches the account and provider update its
- // backoff and effectiveStartTime
- for (SyncOperation op : mOperationsMap.values()) {
- if (op.account.equals(account) && op.authority.equals(providerName)
- && op.userId == userId) {
- op.backoff = backoff;
- op.updateEffectiveRunTime();
- }
- }
- }
-
- public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
- // for each op that matches the account and provider update its
- // delayUntilTime and effectiveStartTime
- for (SyncOperation op : mOperationsMap.values()) {
- if (op.account.equals(account) && op.authority.equals(providerName)) {
- op.delayUntil = delayUntil;
- op.updateEffectiveRunTime();
- }
- }
- }
-
- public void remove(Account account, int userId, String authority) {
- Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
- while (entries.hasNext()) {
- Map.Entry<String, SyncOperation> entry = entries.next();
- SyncOperation syncOperation = entry.getValue();
- if (account != null && !syncOperation.account.equals(account)) {
- continue;
- }
- if (authority != null && !syncOperation.authority.equals(authority)) {
- continue;
- }
- if (userId != syncOperation.userId) {
- continue;
- }
- entries.remove();
- if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
- final String errorMessage = "unable to find pending row for " + syncOperation;
- Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
- }
- }
- }
-
- public Collection<SyncOperation> getOperations() {
- return mOperationsMap.values();
- }
-
- public void dump(StringBuilder sb) {
- final long now = SystemClock.elapsedRealtime();
- sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
- for (SyncOperation operation : mOperationsMap.values()) {
- sb.append(" ");
- if (operation.effectiveRunTime <= now) {
- sb.append("READY");
- } else {
- sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000));
- }
- sb.append(" - ");
- sb.append(operation.dump(false)).append("\n");
- }
- }
-}
diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java
index bb2b2da..ff628d9 100644
--- a/core/java/android/content/SyncStatusInfo.java
+++ b/core/java/android/content/SyncStatusInfo.java
@@ -46,19 +46,18 @@ public class SyncStatusInfo implements Parcelable {
private static final String TAG = "Sync";
- SyncStatusInfo(int authorityId) {
+ public SyncStatusInfo(int authorityId) {
this.authorityId = authorityId;
}
public int getLastFailureMesgAsInt(int def) {
- try {
- if (lastFailureMesg != null) {
- return Integer.parseInt(lastFailureMesg);
- }
- } catch (NumberFormatException e) {
- Log.d(TAG, "error parsing lastFailureMesg of " + lastFailureMesg, e);
+ final int i = ContentResolver.syncErrorStringToInt(lastFailureMesg);
+ if (i > 0) {
+ return i;
+ } else {
+ Log.d(TAG, "Unknown lastFailureMesg:" + lastFailureMesg);
+ return def;
}
- return def;
}
public int describeContents() {
@@ -92,7 +91,7 @@ public class SyncStatusInfo implements Parcelable {
}
}
- SyncStatusInfo(Parcel parcel) {
+ public SyncStatusInfo(Parcel parcel) {
int version = parcel.readInt();
if (version != VERSION && version != 1) {
Log.w("SyncStatusInfo", "Unknown version: " + version);
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
deleted file mode 100644
index 1ecab09..0000000
--- a/core/java/android/content/SyncStorageEngine.java
+++ /dev/null
@@ -1,2303 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FastXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import android.accounts.Account;
-import android.accounts.AccountAndUser;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Parcel;
-import android.os.RemoteCallbackList;
-import android.os.RemoteException;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.Xml;
-import android.util.Pair;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Random;
-import java.util.TimeZone;
-import java.util.List;
-
-/**
- * Singleton that tracks the sync data and overall sync
- * history on the device.
- *
- * @hide
- */
-public class SyncStorageEngine extends Handler {
-
- private static final String TAG = "SyncManager";
- private static final boolean DEBUG = false;
- private static final boolean DEBUG_FILE = false;
-
- private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
- private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles";
- private static final String XML_ATTR_SYNC_RANDOM_OFFSET = "offsetInSeconds";
- private static final String XML_ATTR_ENABLED = "enabled";
- private static final String XML_ATTR_USER = "user";
- private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
-
- private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
-
- @VisibleForTesting
- static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
-
- /** Enum value for a sync start event. */
- public static final int EVENT_START = 0;
-
- /** Enum value for a sync stop event. */
- public static final int EVENT_STOP = 1;
-
- // TODO: i18n -- grab these out of resources.
- /** String names for the sync event types. */
- public static final String[] EVENTS = { "START", "STOP" };
-
- /** Enum value for a server-initiated sync. */
- public static final int SOURCE_SERVER = 0;
-
- /** Enum value for a local-initiated sync. */
- public static final int SOURCE_LOCAL = 1;
- /**
- * Enum value for a poll-based sync (e.g., upon connection to
- * network)
- */
- public static final int SOURCE_POLL = 2;
-
- /** Enum value for a user-initiated sync. */
- public static final int SOURCE_USER = 3;
-
- /** Enum value for a periodic sync. */
- public static final int SOURCE_PERIODIC = 4;
-
- public static final long NOT_IN_BACKOFF_MODE = -1;
-
- public static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
- new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
-
- // TODO: i18n -- grab these out of resources.
- /** String names for the sync source types. */
- public static final String[] SOURCES = { "SERVER",
- "LOCAL",
- "POLL",
- "USER",
- "PERIODIC" };
-
- // The MESG column will contain one of these or one of the Error types.
- public static final String MESG_SUCCESS = "success";
- public static final String MESG_CANCELED = "canceled";
-
- public static final int MAX_HISTORY = 100;
-
- private static final int MSG_WRITE_STATUS = 1;
- private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes
-
- private static final int MSG_WRITE_STATISTICS = 2;
- private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour
-
- private static final boolean SYNC_ENABLED_DEFAULT = false;
-
- // the version of the accounts xml file format
- private static final int ACCOUNTS_VERSION = 2;
-
- private static HashMap<String, String> sAuthorityRenames;
-
- static {
- sAuthorityRenames = new HashMap<String, String>();
- sAuthorityRenames.put("contacts", "com.android.contacts");
- sAuthorityRenames.put("calendar", "com.android.calendar");
- }
-
- public static class PendingOperation {
- final Account account;
- final int userId;
- final int syncSource;
- final String authority;
- final Bundle extras; // note: read-only.
- final boolean expedited;
-
- int authorityId;
- byte[] flatExtras;
-
- PendingOperation(Account account, int userId, int source,
- String authority, Bundle extras, boolean expedited) {
- this.account = account;
- this.userId = userId;
- this.syncSource = source;
- this.authority = authority;
- this.extras = extras != null ? new Bundle(extras) : extras;
- this.expedited = expedited;
- this.authorityId = -1;
- }
-
- PendingOperation(PendingOperation other) {
- this.account = other.account;
- this.userId = other.userId;
- this.syncSource = other.syncSource;
- this.authority = other.authority;
- this.extras = other.extras;
- this.authorityId = other.authorityId;
- this.expedited = other.expedited;
- }
- }
-
- static class AccountInfo {
- final AccountAndUser accountAndUser;
- final HashMap<String, AuthorityInfo> authorities =
- new HashMap<String, AuthorityInfo>();
-
- AccountInfo(AccountAndUser accountAndUser) {
- this.accountAndUser = accountAndUser;
- }
- }
-
- public static class AuthorityInfo {
- final Account account;
- final int userId;
- final String authority;
- final int ident;
- boolean enabled;
- int syncable;
- long backoffTime;
- long backoffDelay;
- long delayUntil;
- final ArrayList<Pair<Bundle, Long>> periodicSyncs;
-
- /**
- * Copy constructor for making deep-ish copies. Only the bundles stored
- * in periodic syncs can make unexpected changes.
- *
- * @param toCopy AuthorityInfo to be copied.
- */
- AuthorityInfo(AuthorityInfo toCopy) {
- account = toCopy.account;
- userId = toCopy.userId;
- authority = toCopy.authority;
- ident = toCopy.ident;
- enabled = toCopy.enabled;
- syncable = toCopy.syncable;
- backoffTime = toCopy.backoffTime;
- backoffDelay = toCopy.backoffDelay;
- delayUntil = toCopy.delayUntil;
- periodicSyncs = new ArrayList<Pair<Bundle, Long>>();
- for (Pair<Bundle, Long> sync : toCopy.periodicSyncs) {
- // Still not a perfect copy, because we are just copying the mappings.
- periodicSyncs.add(Pair.create(new Bundle(sync.first), sync.second));
- }
- }
-
- AuthorityInfo(Account account, int userId, String authority, int ident) {
- this.account = account;
- this.userId = userId;
- this.authority = authority;
- this.ident = ident;
- enabled = SYNC_ENABLED_DEFAULT;
- syncable = -1; // default to "unknown"
- backoffTime = -1; // if < 0 then we aren't in backoff mode
- backoffDelay = -1; // if < 0 then we aren't in backoff mode
- periodicSyncs = new ArrayList<Pair<Bundle, Long>>();
- periodicSyncs.add(Pair.create(new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS));
- }
- }
-
- public static class SyncHistoryItem {
- int authorityId;
- int historyId;
- long eventTime;
- long elapsedTime;
- int source;
- int event;
- long upstreamActivity;
- long downstreamActivity;
- String mesg;
- boolean initialization;
- }
-
- public static class DayStats {
- public final int day;
- public int successCount;
- public long successTime;
- public int failureCount;
- public long failureTime;
-
- public DayStats(int day) {
- this.day = day;
- }
- }
-
- interface OnSyncRequestListener {
- /**
- * Called when a sync is needed on an account(s) due to some change in state.
- * @param account
- * @param userId
- * @param authority
- * @param extras
- */
- public void onSyncRequest(Account account, int userId, String authority, Bundle extras);
- }
-
- // Primary list of all syncable authorities. Also our global lock.
- private final SparseArray<AuthorityInfo> mAuthorities =
- new SparseArray<AuthorityInfo>();
-
- private final HashMap<AccountAndUser, AccountInfo> mAccounts
- = new HashMap<AccountAndUser, AccountInfo>();
-
- private final ArrayList<PendingOperation> mPendingOperations =
- new ArrayList<PendingOperation>();
-
- private final SparseArray<ArrayList<SyncInfo>> mCurrentSyncs
- = new SparseArray<ArrayList<SyncInfo>>();
-
- private final SparseArray<SyncStatusInfo> mSyncStatus =
- new SparseArray<SyncStatusInfo>();
-
- private final ArrayList<SyncHistoryItem> mSyncHistory =
- new ArrayList<SyncHistoryItem>();
-
- private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
- = new RemoteCallbackList<ISyncStatusObserver>();
-
- private int mNextAuthorityId = 0;
-
- // We keep 4 weeks of stats.
- private final DayStats[] mDayStats = new DayStats[7*4];
- private final Calendar mCal;
- private int mYear;
- private int mYearInDays;
-
- private final Context mContext;
-
- private static volatile SyncStorageEngine sSyncStorageEngine = null;
-
- private int mSyncRandomOffset;
-
- /**
- * This file contains the core engine state: all accounts and the
- * settings for them. It must never be lost, and should be changed
- * infrequently, so it is stored as an XML file.
- */
- private final AtomicFile mAccountInfoFile;
-
- /**
- * This file contains the current sync status. We would like to retain
- * it across boots, but its loss is not the end of the world, so we store
- * this information as binary data.
- */
- private final AtomicFile mStatusFile;
-
- /**
- * This file contains sync statistics. This is purely debugging information
- * so is written infrequently and can be thrown away at any time.
- */
- private final AtomicFile mStatisticsFile;
-
- /**
- * This file contains the pending sync operations. It is a binary file,
- * which must be updated every time an operation is added or removed,
- * so we have special handling of it.
- */
- private final AtomicFile mPendingFile;
- private static final int PENDING_FINISH_TO_WRITE = 4;
- private int mNumPendingFinished = 0;
-
- private int mNextHistoryId = 0;
- private SparseArray<Boolean> mMasterSyncAutomatically = new SparseArray<Boolean>();
- private boolean mDefaultMasterSyncAutomatically;
-
- private OnSyncRequestListener mSyncRequestListener;
-
- private SyncStorageEngine(Context context, File dataDir) {
- mContext = context;
- sSyncStorageEngine = this;
-
- mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
-
- mDefaultMasterSyncAutomatically = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_syncstorageengine_masterSyncAutomatically);
-
- File systemDir = new File(dataDir, "system");
- File syncDir = new File(systemDir, "sync");
- syncDir.mkdirs();
- mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
- mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
- mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
- mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"));
-
- readAccountInfoLocked();
- readStatusLocked();
- readPendingOperationsLocked();
- readStatisticsLocked();
- readAndDeleteLegacyAccountInfoLocked();
- writeAccountInfoLocked();
- writeStatusLocked();
- writePendingOperationsLocked();
- writeStatisticsLocked();
- }
-
- public static SyncStorageEngine newTestInstance(Context context) {
- return new SyncStorageEngine(context, context.getFilesDir());
- }
-
- public static void init(Context context) {
- if (sSyncStorageEngine != null) {
- return;
- }
- // This call will return the correct directory whether Encrypted File Systems is
- // enabled or not.
- File dataDir = Environment.getSecureDataDirectory();
- sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
- }
-
- public static SyncStorageEngine getSingleton() {
- if (sSyncStorageEngine == null) {
- throw new IllegalStateException("not initialized");
- }
- return sSyncStorageEngine;
- }
-
- protected void setOnSyncRequestListener(OnSyncRequestListener listener) {
- if (mSyncRequestListener == null) {
- mSyncRequestListener = listener;
- }
- }
-
- @Override public void handleMessage(Message msg) {
- if (msg.what == MSG_WRITE_STATUS) {
- synchronized (mAuthorities) {
- writeStatusLocked();
- }
- } else if (msg.what == MSG_WRITE_STATISTICS) {
- synchronized (mAuthorities) {
- writeStatisticsLocked();
- }
- }
- }
-
- public int getSyncRandomOffset() {
- return mSyncRandomOffset;
- }
-
- public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
- synchronized (mAuthorities) {
- mChangeListeners.register(callback, mask);
- }
- }
-
- public void removeStatusChangeListener(ISyncStatusObserver callback) {
- synchronized (mAuthorities) {
- mChangeListeners.unregister(callback);
- }
- }
-
- private void reportChange(int which) {
- ArrayList<ISyncStatusObserver> reports = null;
- synchronized (mAuthorities) {
- int i = mChangeListeners.beginBroadcast();
- while (i > 0) {
- i--;
- Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i);
- if ((which & mask.intValue()) == 0) {
- continue;
- }
- if (reports == null) {
- reports = new ArrayList<ISyncStatusObserver>(i);
- }
- reports.add(mChangeListeners.getBroadcastItem(i));
- }
- mChangeListeners.finishBroadcast();
- }
-
- if (DEBUG) {
- Log.v(TAG, "reportChange " + which + " to: " + reports);
- }
-
- if (reports != null) {
- int i = reports.size();
- while (i > 0) {
- i--;
- try {
- reports.get(i).onStatusChanged(which);
- } catch (RemoteException e) {
- // The remote callback list will take care of this for us.
- }
- }
- }
- }
-
- public boolean getSyncAutomatically(Account account, int userId, String providerName) {
- synchronized (mAuthorities) {
- if (account != null) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getSyncAutomatically");
- return authority != null && authority.enabled;
- }
-
- int i = mAuthorities.size();
- while (i > 0) {
- i--;
- AuthorityInfo authority = mAuthorities.valueAt(i);
- if (authority.authority.equals(providerName)
- && authority.userId == userId
- && authority.enabled) {
- return true;
- }
- }
- return false;
- }
- }
-
- public void setSyncAutomatically(Account account, int userId, String providerName,
- boolean sync) {
- if (DEBUG) {
- Log.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName
- + ", user " + userId + " -> " + sync);
- }
- synchronized (mAuthorities) {
- AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1,
- false);
- if (authority.enabled == sync) {
- if (DEBUG) {
- Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing");
- }
- return;
- }
- authority.enabled = sync;
- writeAccountInfoLocked();
- }
-
- if (sync) {
- requestSync(account, userId, providerName, new Bundle());
- }
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- }
-
- public int getIsSyncable(Account account, int userId, String providerName) {
- synchronized (mAuthorities) {
- if (account != null) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getIsSyncable");
- if (authority == null) {
- return -1;
- }
- return authority.syncable;
- }
-
- int i = mAuthorities.size();
- while (i > 0) {
- i--;
- AuthorityInfo authority = mAuthorities.valueAt(i);
- if (authority.authority.equals(providerName)) {
- return authority.syncable;
- }
- }
- return -1;
- }
- }
-
- public void setIsSyncable(Account account, int userId, String providerName, int syncable) {
- if (syncable > 1) {
- syncable = 1;
- } else if (syncable < -1) {
- syncable = -1;
- }
- if (DEBUG) {
- Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName
- + ", user " + userId + " -> " + syncable);
- }
- synchronized (mAuthorities) {
- AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1,
- false);
- if (authority.syncable == syncable) {
- if (DEBUG) {
- Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
- }
- return;
- }
- authority.syncable = syncable;
- writeAccountInfoLocked();
- }
-
- if (syncable > 0) {
- requestSync(account, userId, providerName, new Bundle());
- }
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- }
-
- public Pair<Long, Long> getBackoff(Account account, int userId, String providerName) {
- synchronized (mAuthorities) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getBackoff");
- if (authority == null || authority.backoffTime < 0) {
- return null;
- }
- return Pair.create(authority.backoffTime, authority.backoffDelay);
- }
- }
-
- public void setBackoff(Account account, int userId, String providerName,
- long nextSyncTime, long nextDelay) {
- if (DEBUG) {
- Log.v(TAG, "setBackoff: " + account + ", provider " + providerName
- + ", user " + userId
- + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay);
- }
- boolean changed = false;
- synchronized (mAuthorities) {
- if (account == null || providerName == null) {
- for (AccountInfo accountInfo : mAccounts.values()) {
- if (account != null && !account.equals(accountInfo.accountAndUser.account)
- && userId != accountInfo.accountAndUser.userId) {
- continue;
- }
- for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
- if (providerName != null && !providerName.equals(authorityInfo.authority)) {
- continue;
- }
- if (authorityInfo.backoffTime != nextSyncTime
- || authorityInfo.backoffDelay != nextDelay) {
- authorityInfo.backoffTime = nextSyncTime;
- authorityInfo.backoffDelay = nextDelay;
- changed = true;
- }
- }
- }
- } else {
- AuthorityInfo authority =
- getOrCreateAuthorityLocked(account, userId, providerName, -1 /* ident */,
- true);
- if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
- return;
- }
- authority.backoffTime = nextSyncTime;
- authority.backoffDelay = nextDelay;
- changed = true;
- }
- }
-
- if (changed) {
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- }
- }
-
- public void clearAllBackoffs(SyncQueue syncQueue) {
- boolean changed = false;
- synchronized (mAuthorities) {
- synchronized (syncQueue) {
- for (AccountInfo accountInfo : mAccounts.values()) {
- for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
- if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE
- || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) {
- if (DEBUG) {
- Log.v(TAG, "clearAllBackoffs:"
- + " authority:" + authorityInfo.authority
- + " account:" + accountInfo.accountAndUser.account.name
- + " user:" + accountInfo.accountAndUser.userId
- + " backoffTime was: " + authorityInfo.backoffTime
- + " backoffDelay was: " + authorityInfo.backoffDelay);
- }
- authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
- authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
- syncQueue.onBackoffChanged(accountInfo.accountAndUser.account,
- accountInfo.accountAndUser.userId, authorityInfo.authority, 0);
- changed = true;
- }
- }
- }
- }
- }
-
- if (changed) {
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- }
- }
-
- public void setDelayUntilTime(Account account, int userId, String providerName,
- long delayUntil) {
- if (DEBUG) {
- Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName
- + ", user " + userId + " -> delayUntil " + delayUntil);
- }
- synchronized (mAuthorities) {
- AuthorityInfo authority = getOrCreateAuthorityLocked(
- account, userId, providerName, -1 /* ident */, true);
- if (authority.delayUntil == delayUntil) {
- return;
- }
- authority.delayUntil = delayUntil;
- }
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- }
-
- public long getDelayUntilTime(Account account, int userId, String providerName) {
- synchronized (mAuthorities) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getDelayUntil");
- if (authority == null) {
- return 0;
- }
- return authority.delayUntil;
- }
- }
-
- private void updateOrRemovePeriodicSync(Account account, int userId, String providerName,
- Bundle extras,
- long period, boolean add) {
- if (period <= 0) {
- period = 0;
- }
- if (extras == null) {
- extras = new Bundle();
- }
- if (DEBUG) {
- Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", user " + userId
- + ", provider " + providerName
- + " -> period " + period + ", extras " + extras);
- }
- synchronized (mAuthorities) {
- try {
- AuthorityInfo authority =
- getOrCreateAuthorityLocked(account, userId, providerName, -1, false);
- if (add) {
- // add this periodic sync if one with the same extras doesn't already
- // exist in the periodicSyncs array
- boolean alreadyPresent = false;
- for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
- Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i);
- final Bundle existingExtras = syncInfo.first;
- if (equals(existingExtras, extras)) {
- if (syncInfo.second == period) {
- return;
- }
- authority.periodicSyncs.set(i, Pair.create(extras, period));
- alreadyPresent = true;
- break;
- }
- }
- // if we added an entry to the periodicSyncs array also add an entry to
- // the periodic syncs status to correspond to it
- if (!alreadyPresent) {
- authority.periodicSyncs.add(Pair.create(extras, period));
- SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
- status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0);
- }
- } else {
- // remove any periodic syncs that match the authority and extras
- SyncStatusInfo status = mSyncStatus.get(authority.ident);
- boolean changed = false;
- Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator();
- int i = 0;
- while (iterator.hasNext()) {
- Pair<Bundle, Long> syncInfo = iterator.next();
- if (equals(syncInfo.first, extras)) {
- iterator.remove();
- changed = true;
- // if we removed an entry from the periodicSyncs array also
- // remove the corresponding entry from the status
- if (status != null) {
- status.removePeriodicSyncTime(i);
- }
- } else {
- i++;
- }
- }
- if (!changed) {
- return;
- }
- }
- } finally {
- writeAccountInfoLocked();
- writeStatusLocked();
- }
- }
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- }
-
- public void addPeriodicSync(Account account, int userId, String providerName, Bundle extras,
- long pollFrequency) {
- updateOrRemovePeriodicSync(account, userId, providerName, extras, pollFrequency,
- true /* add */);
- }
-
- public void removePeriodicSync(Account account, int userId, String providerName,
- Bundle extras) {
- updateOrRemovePeriodicSync(account, userId, providerName, extras, 0 /* period, ignored */,
- false /* remove */);
- }
-
- public List<PeriodicSync> getPeriodicSyncs(Account account, int userId, String providerName) {
- ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
- synchronized (mAuthorities) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getPeriodicSyncs");
- if (authority != null) {
- for (Pair<Bundle, Long> item : authority.periodicSyncs) {
- syncs.add(new PeriodicSync(account, providerName, item.first,
- item.second));
- }
- }
- }
- return syncs;
- }
-
- public void setMasterSyncAutomatically(boolean flag, int userId) {
- synchronized (mAuthorities) {
- Boolean auto = mMasterSyncAutomatically.get(userId);
- if (auto != null && (boolean) auto == flag) {
- return;
- }
- mMasterSyncAutomatically.put(userId, flag);
- writeAccountInfoLocked();
- }
- if (flag) {
- requestSync(null, userId, null, new Bundle());
- }
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT);
- }
-
- public boolean getMasterSyncAutomatically(int userId) {
- synchronized (mAuthorities) {
- Boolean auto = mMasterSyncAutomatically.get(userId);
- return auto == null ? mDefaultMasterSyncAutomatically : auto;
- }
- }
-
- public AuthorityInfo getOrCreateAuthority(Account account, int userId, String authority) {
- synchronized (mAuthorities) {
- return getOrCreateAuthorityLocked(account, userId, authority,
- -1 /* assign a new identifier if creating a new authority */,
- true /* write to storage if this results in a change */);
- }
- }
-
- public void removeAuthority(Account account, int userId, String authority) {
- synchronized (mAuthorities) {
- removeAuthorityLocked(account, userId, authority, true /* doWrite */);
- }
- }
-
- public AuthorityInfo getAuthority(int authorityId) {
- synchronized (mAuthorities) {
- return mAuthorities.get(authorityId);
- }
- }
-
- /**
- * Returns true if there is currently a sync operation for the given
- * account or authority actively being processed.
- */
- public boolean isSyncActive(Account account, int userId, String authority) {
- synchronized (mAuthorities) {
- for (SyncInfo syncInfo : getCurrentSyncs(userId)) {
- AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
- if (ainfo != null && ainfo.account.equals(account)
- && ainfo.authority.equals(authority)
- && ainfo.userId == userId) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- public PendingOperation insertIntoPending(PendingOperation op) {
- synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "insertIntoPending: account=" + op.account
- + " user=" + op.userId
- + " auth=" + op.authority
- + " src=" + op.syncSource
- + " extras=" + op.extras);
- }
-
- AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, op.userId,
- op.authority,
- -1 /* desired identifier */,
- true /* write accounts to storage */);
- if (authority == null) {
- return null;
- }
-
- op = new PendingOperation(op);
- op.authorityId = authority.ident;
- mPendingOperations.add(op);
- appendPendingOperationLocked(op);
-
- SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
- status.pending = true;
- }
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
- return op;
- }
-
- public boolean deleteFromPending(PendingOperation op) {
- boolean res = false;
- synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "deleteFromPending: account=" + op.account
- + " user=" + op.userId
- + " auth=" + op.authority
- + " src=" + op.syncSource
- + " extras=" + op.extras);
- }
- if (mPendingOperations.remove(op)) {
- if (mPendingOperations.size() == 0
- || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
- writePendingOperationsLocked();
- mNumPendingFinished = 0;
- } else {
- mNumPendingFinished++;
- }
-
- AuthorityInfo authority = getAuthorityLocked(op.account, op.userId, op.authority,
- "deleteFromPending");
- if (authority != null) {
- if (DEBUG) Log.v(TAG, "removing - " + authority);
- final int N = mPendingOperations.size();
- boolean morePending = false;
- for (int i=0; i<N; i++) {
- PendingOperation cur = mPendingOperations.get(i);
- if (cur.account.equals(op.account)
- && cur.authority.equals(op.authority)
- && cur.userId == op.userId) {
- morePending = true;
- break;
- }
- }
-
- if (!morePending) {
- if (DEBUG) Log.v(TAG, "no more pending!");
- SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
- status.pending = false;
- }
- }
-
- res = true;
- }
- }
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
- return res;
- }
-
- /**
- * Return a copy of the current array of pending operations. The
- * PendingOperation objects are the real objects stored inside, so that
- * they can be used with deleteFromPending().
- */
- public ArrayList<PendingOperation> getPendingOperations() {
- synchronized (mAuthorities) {
- return new ArrayList<PendingOperation>(mPendingOperations);
- }
- }
-
- /**
- * Return the number of currently pending operations.
- */
- public int getPendingOperationCount() {
- synchronized (mAuthorities) {
- return mPendingOperations.size();
- }
- }
-
- /**
- * Called when the set of account has changed, given the new array of
- * active accounts.
- */
- public void doDatabaseCleanup(Account[] accounts, int userId) {
- synchronized (mAuthorities) {
- if (DEBUG) Log.v(TAG, "Updating for new accounts...");
- SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
- Iterator<AccountInfo> accIt = mAccounts.values().iterator();
- while (accIt.hasNext()) {
- AccountInfo acc = accIt.next();
- if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
- && acc.accountAndUser.userId == userId) {
- // This account no longer exists...
- if (DEBUG) {
- Log.v(TAG, "Account removed: " + acc.accountAndUser);
- }
- for (AuthorityInfo auth : acc.authorities.values()) {
- removing.put(auth.ident, auth);
- }
- accIt.remove();
- }
- }
-
- // Clean out all data structures.
- int i = removing.size();
- if (i > 0) {
- while (i > 0) {
- i--;
- int ident = removing.keyAt(i);
- mAuthorities.remove(ident);
- int j = mSyncStatus.size();
- while (j > 0) {
- j--;
- if (mSyncStatus.keyAt(j) == ident) {
- mSyncStatus.remove(mSyncStatus.keyAt(j));
- }
- }
- j = mSyncHistory.size();
- while (j > 0) {
- j--;
- if (mSyncHistory.get(j).authorityId == ident) {
- mSyncHistory.remove(j);
- }
- }
- }
- writeAccountInfoLocked();
- writeStatusLocked();
- writePendingOperationsLocked();
- writeStatisticsLocked();
- }
- }
- }
-
- /**
- * Called when a sync is starting. Supply a valid ActiveSyncContext with information
- * about the sync.
- */
- public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
- final SyncInfo syncInfo;
- synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "setActiveSync: account="
- + activeSyncContext.mSyncOperation.account
- + " auth=" + activeSyncContext.mSyncOperation.authority
- + " src=" + activeSyncContext.mSyncOperation.syncSource
- + " extras=" + activeSyncContext.mSyncOperation.extras);
- }
- AuthorityInfo authority = getOrCreateAuthorityLocked(
- activeSyncContext.mSyncOperation.account,
- activeSyncContext.mSyncOperation.userId,
- activeSyncContext.mSyncOperation.authority,
- -1 /* assign a new identifier if creating a new authority */,
- true /* write to storage if this results in a change */);
- syncInfo = new SyncInfo(authority.ident,
- authority.account, authority.authority,
- activeSyncContext.mStartTime);
- getCurrentSyncs(authority.userId).add(syncInfo);
- }
-
- reportActiveChange();
- return syncInfo;
- }
-
- /**
- * Called to indicate that a previously active sync is no longer active.
- */
- public void removeActiveSync(SyncInfo syncInfo, int userId) {
- synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "removeActiveSync: account=" + syncInfo.account
- + " user=" + userId
- + " auth=" + syncInfo.authority);
- }
- getCurrentSyncs(userId).remove(syncInfo);
- }
-
- reportActiveChange();
- }
-
- /**
- * To allow others to send active change reports, to poke clients.
- */
- public void reportActiveChange() {
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
- }
-
- /**
- * Note that sync has started for the given account and authority.
- */
- public long insertStartSyncEvent(Account accountName, int userId, String authorityName,
- long now, int source, boolean initialization) {
- long id;
- synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "insertStartSyncEvent: account=" + accountName + "user=" + userId
- + " auth=" + authorityName + " source=" + source);
- }
- AuthorityInfo authority = getAuthorityLocked(accountName, userId, authorityName,
- "insertStartSyncEvent");
- if (authority == null) {
- return -1;
- }
- SyncHistoryItem item = new SyncHistoryItem();
- item.initialization = initialization;
- item.authorityId = authority.ident;
- item.historyId = mNextHistoryId++;
- if (mNextHistoryId < 0) mNextHistoryId = 0;
- item.eventTime = now;
- item.source = source;
- item.event = EVENT_START;
- mSyncHistory.add(0, item);
- while (mSyncHistory.size() > MAX_HISTORY) {
- mSyncHistory.remove(mSyncHistory.size()-1);
- }
- id = item.historyId;
- if (DEBUG) Log.v(TAG, "returning historyId " + id);
- }
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
- return id;
- }
-
- public static boolean equals(Bundle b1, Bundle b2) {
- if (b1.size() != b2.size()) {
- return false;
- }
- if (b1.isEmpty()) {
- return true;
- }
- for (String key : b1.keySet()) {
- if (!b2.containsKey(key)) {
- return false;
- }
- if (!b1.get(key).equals(b2.get(key))) {
- return false;
- }
- }
- return true;
- }
-
- public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
- long downstreamActivity, long upstreamActivity) {
- synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
- }
- SyncHistoryItem item = null;
- int i = mSyncHistory.size();
- while (i > 0) {
- i--;
- item = mSyncHistory.get(i);
- if (item.historyId == historyId) {
- break;
- }
- item = null;
- }
-
- if (item == null) {
- Log.w(TAG, "stopSyncEvent: no history for id " + historyId);
- return;
- }
-
- item.elapsedTime = elapsedTime;
- item.event = EVENT_STOP;
- item.mesg = resultMessage;
- item.downstreamActivity = downstreamActivity;
- item.upstreamActivity = upstreamActivity;
-
- SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
-
- status.numSyncs++;
- status.totalElapsedTime += elapsedTime;
- switch (item.source) {
- case SOURCE_LOCAL:
- status.numSourceLocal++;
- break;
- case SOURCE_POLL:
- status.numSourcePoll++;
- break;
- case SOURCE_USER:
- status.numSourceUser++;
- break;
- case SOURCE_SERVER:
- status.numSourceServer++;
- break;
- case SOURCE_PERIODIC:
- status.numSourcePeriodic++;
- break;
- }
-
- boolean writeStatisticsNow = false;
- int day = getCurrentDayLocked();
- if (mDayStats[0] == null) {
- mDayStats[0] = new DayStats(day);
- } else if (day != mDayStats[0].day) {
- System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
- mDayStats[0] = new DayStats(day);
- writeStatisticsNow = true;
- } else if (mDayStats[0] == null) {
- }
- final DayStats ds = mDayStats[0];
-
- final long lastSyncTime = (item.eventTime + elapsedTime);
- boolean writeStatusNow = false;
- if (MESG_SUCCESS.equals(resultMessage)) {
- // - if successful, update the successful columns
- if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
- writeStatusNow = true;
- }
- status.lastSuccessTime = lastSyncTime;
- status.lastSuccessSource = item.source;
- status.lastFailureTime = 0;
- status.lastFailureSource = -1;
- status.lastFailureMesg = null;
- status.initialFailureTime = 0;
- ds.successCount++;
- ds.successTime += elapsedTime;
- } else if (!MESG_CANCELED.equals(resultMessage)) {
- if (status.lastFailureTime == 0) {
- writeStatusNow = true;
- }
- status.lastFailureTime = lastSyncTime;
- status.lastFailureSource = item.source;
- status.lastFailureMesg = resultMessage;
- if (status.initialFailureTime == 0) {
- status.initialFailureTime = lastSyncTime;
- }
- ds.failureCount++;
- ds.failureTime += elapsedTime;
- }
-
- if (writeStatusNow) {
- writeStatusLocked();
- } else if (!hasMessages(MSG_WRITE_STATUS)) {
- sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
- WRITE_STATUS_DELAY);
- }
- if (writeStatisticsNow) {
- writeStatisticsLocked();
- } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
- sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
- WRITE_STATISTICS_DELAY);
- }
- }
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
- }
-
- /**
- * Return a list of the currently active syncs. Note that the returned items are the
- * real, live active sync objects, so be careful what you do with it.
- */
- public List<SyncInfo> getCurrentSyncs(int userId) {
- synchronized (mAuthorities) {
- ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId);
- if (syncs == null) {
- syncs = new ArrayList<SyncInfo>();
- mCurrentSyncs.put(userId, syncs);
- }
- return syncs;
- }
- }
-
- /**
- * Return an array of the current sync status for all authorities. Note
- * that the objects inside the array are the real, live status objects,
- * so be careful what you do with them.
- */
- public ArrayList<SyncStatusInfo> getSyncStatus() {
- synchronized (mAuthorities) {
- final int N = mSyncStatus.size();
- ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N);
- for (int i=0; i<N; i++) {
- ops.add(mSyncStatus.valueAt(i));
- }
- return ops;
- }
- }
-
- /**
- * Return an array of the current authorities. Note
- * that the objects inside the array are the real, live objects,
- * so be careful what you do with them.
- */
- public ArrayList<AuthorityInfo> getAuthorities() {
- synchronized (mAuthorities) {
- final int N = mAuthorities.size();
- ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N);
- for (int i=0; i<N; i++) {
- // Make deep copy because AuthorityInfo syncs are liable to change.
- infos.add(new AuthorityInfo(mAuthorities.valueAt(i)));
- }
- return infos;
- }
- }
-
- /**
- * Returns the status that matches the authority and account.
- *
- * @param account the account we want to check
- * @param authority the authority whose row should be selected
- * @return the SyncStatusInfo for the authority
- */
- public SyncStatusInfo getStatusByAccountAndAuthority(Account account, int userId,
- String authority) {
- if (account == null || authority == null) {
- throw new IllegalArgumentException();
- }
- synchronized (mAuthorities) {
- final int N = mSyncStatus.size();
- for (int i=0; i<N; i++) {
- SyncStatusInfo cur = mSyncStatus.valueAt(i);
- AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
-
- if (ainfo != null && ainfo.authority.equals(authority)
- && ainfo.userId == userId
- && account.equals(ainfo.account)) {
- return cur;
- }
- }
- return null;
- }
- }
-
- /**
- * Return true if the pending status is true of any matching authorities.
- */
- public boolean isSyncPending(Account account, int userId, String authority) {
- synchronized (mAuthorities) {
- final int N = mSyncStatus.size();
- for (int i=0; i<N; i++) {
- SyncStatusInfo cur = mSyncStatus.valueAt(i);
- AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
- if (ainfo == null) {
- continue;
- }
- if (userId != ainfo.userId) {
- continue;
- }
- if (account != null && !ainfo.account.equals(account)) {
- continue;
- }
- if (ainfo.authority.equals(authority) && cur.pending) {
- return true;
- }
- }
- return false;
- }
- }
-
- /**
- * Return an array of the current sync status for all authorities. Note
- * that the objects inside the array are the real, live status objects,
- * so be careful what you do with them.
- */
- public ArrayList<SyncHistoryItem> getSyncHistory() {
- synchronized (mAuthorities) {
- final int N = mSyncHistory.size();
- ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
- for (int i=0; i<N; i++) {
- items.add(mSyncHistory.get(i));
- }
- return items;
- }
- }
-
- /**
- * Return an array of the current per-day statistics. Note
- * that the objects inside the array are the real, live status objects,
- * so be careful what you do with them.
- */
- public DayStats[] getDayStatistics() {
- synchronized (mAuthorities) {
- DayStats[] ds = new DayStats[mDayStats.length];
- System.arraycopy(mDayStats, 0, ds, 0, ds.length);
- return ds;
- }
- }
-
- private int getCurrentDayLocked() {
- mCal.setTimeInMillis(System.currentTimeMillis());
- final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
- if (mYear != mCal.get(Calendar.YEAR)) {
- mYear = mCal.get(Calendar.YEAR);
- mCal.clear();
- mCal.set(Calendar.YEAR, mYear);
- mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
- }
- return dayOfYear + mYearInDays;
- }
-
- /**
- * Retrieve an authority, returning null if one does not exist.
- *
- * @param accountName The name of the account for the authority.
- * @param authorityName The name of the authority itself.
- * @param tag If non-null, this will be used in a log message if the
- * requested authority does not exist.
- */
- private AuthorityInfo getAuthorityLocked(Account accountName, int userId, String authorityName,
- String tag) {
- AccountAndUser au = new AccountAndUser(accountName, userId);
- AccountInfo accountInfo = mAccounts.get(au);
- if (accountInfo == null) {
- if (tag != null) {
- if (DEBUG) {
- Log.v(TAG, tag + ": unknown account " + au);
- }
- }
- return null;
- }
- AuthorityInfo authority = accountInfo.authorities.get(authorityName);
- if (authority == null) {
- if (tag != null) {
- if (DEBUG) {
- Log.v(TAG, tag + ": unknown authority " + authorityName);
- }
- }
- return null;
- }
-
- return authority;
- }
-
- private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, int userId,
- String authorityName, int ident, boolean doWrite) {
- AccountAndUser au = new AccountAndUser(accountName, userId);
- AccountInfo account = mAccounts.get(au);
- if (account == null) {
- account = new AccountInfo(au);
- mAccounts.put(au, account);
- }
- AuthorityInfo authority = account.authorities.get(authorityName);
- if (authority == null) {
- if (ident < 0) {
- ident = mNextAuthorityId;
- mNextAuthorityId++;
- doWrite = true;
- }
- if (DEBUG) {
- Log.v(TAG, "created a new AuthorityInfo for " + accountName
- + ", user " + userId
- + ", provider " + authorityName);
- }
- authority = new AuthorityInfo(accountName, userId, authorityName, ident);
- account.authorities.put(authorityName, authority);
- mAuthorities.put(ident, authority);
- if (doWrite) {
- writeAccountInfoLocked();
- }
- }
-
- return authority;
- }
-
- private void removeAuthorityLocked(Account account, int userId, String authorityName,
- boolean doWrite) {
- AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId));
- if (accountInfo != null) {
- final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
- if (authorityInfo != null) {
- mAuthorities.remove(authorityInfo.ident);
- if (doWrite) {
- writeAccountInfoLocked();
- }
- }
- }
- }
-
- public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) {
- synchronized (mAuthorities) {
- return getOrCreateSyncStatusLocked(authority.ident);
- }
- }
-
- private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
- SyncStatusInfo status = mSyncStatus.get(authorityId);
- if (status == null) {
- status = new SyncStatusInfo(authorityId);
- mSyncStatus.put(authorityId, status);
- }
- return status;
- }
-
- public void writeAllState() {
- synchronized (mAuthorities) {
- // Account info is always written so no need to do it here.
-
- if (mNumPendingFinished > 0) {
- // Only write these if they are out of date.
- writePendingOperationsLocked();
- }
-
- // Just always write these... they are likely out of date.
- writeStatusLocked();
- writeStatisticsLocked();
- }
- }
-
- /**
- * public for testing
- */
- public void clearAndReadState() {
- synchronized (mAuthorities) {
- mAuthorities.clear();
- mAccounts.clear();
- mPendingOperations.clear();
- mSyncStatus.clear();
- mSyncHistory.clear();
-
- readAccountInfoLocked();
- readStatusLocked();
- readPendingOperationsLocked();
- readStatisticsLocked();
- readAndDeleteLegacyAccountInfoLocked();
- writeAccountInfoLocked();
- writeStatusLocked();
- writePendingOperationsLocked();
- writeStatisticsLocked();
- }
- }
-
- /**
- * Read all account information back in to the initial engine state.
- */
- private void readAccountInfoLocked() {
- int highestAuthorityId = -1;
- FileInputStream fis = null;
- try {
- fis = mAccountInfoFile.openRead();
- if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile());
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(fis, null);
- int eventType = parser.getEventType();
- while (eventType != XmlPullParser.START_TAG) {
- eventType = parser.next();
- }
- String tagName = parser.getName();
- if ("accounts".equals(tagName)) {
- String listen = parser.getAttributeValue(null, XML_ATTR_LISTEN_FOR_TICKLES);
- String versionString = parser.getAttributeValue(null, "version");
- int version;
- try {
- version = (versionString == null) ? 0 : Integer.parseInt(versionString);
- } catch (NumberFormatException e) {
- version = 0;
- }
- String nextIdString = parser.getAttributeValue(null, XML_ATTR_NEXT_AUTHORITY_ID);
- try {
- int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString);
- mNextAuthorityId = Math.max(mNextAuthorityId, id);
- } catch (NumberFormatException e) {
- // don't care
- }
- String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET);
- try {
- mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString);
- } catch (NumberFormatException e) {
- mSyncRandomOffset = 0;
- }
- if (mSyncRandomOffset == 0) {
- Random random = new Random(System.currentTimeMillis());
- mSyncRandomOffset = random.nextInt(86400);
- }
- mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
- eventType = parser.next();
- AuthorityInfo authority = null;
- Pair<Bundle, Long> periodicSync = null;
- do {
- if (eventType == XmlPullParser.START_TAG) {
- tagName = parser.getName();
- if (parser.getDepth() == 2) {
- if ("authority".equals(tagName)) {
- authority = parseAuthority(parser, version);
- periodicSync = null;
- if (authority.ident > highestAuthorityId) {
- highestAuthorityId = authority.ident;
- }
- } else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) {
- parseListenForTickles(parser);
- }
- } else if (parser.getDepth() == 3) {
- if ("periodicSync".equals(tagName) && authority != null) {
- periodicSync = parsePeriodicSync(parser, authority);
- }
- } else if (parser.getDepth() == 4 && periodicSync != null) {
- if ("extra".equals(tagName)) {
- parseExtra(parser, periodicSync);
- }
- }
- }
- eventType = parser.next();
- } while (eventType != XmlPullParser.END_DOCUMENT);
- }
- } catch (XmlPullParserException e) {
- Log.w(TAG, "Error reading accounts", e);
- return;
- } catch (java.io.IOException e) {
- if (fis == null) Log.i(TAG, "No initial accounts");
- else Log.w(TAG, "Error reading accounts", e);
- return;
- } finally {
- mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId);
- if (fis != null) {
- try {
- fis.close();
- } catch (java.io.IOException e1) {
- }
- }
- }
-
- maybeMigrateSettingsForRenamedAuthorities();
- }
-
- /**
- * some authority names have changed. copy over their settings and delete the old ones
- * @return true if a change was made
- */
- private boolean maybeMigrateSettingsForRenamedAuthorities() {
- boolean writeNeeded = false;
-
- ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
- final int N = mAuthorities.size();
- for (int i=0; i<N; i++) {
- AuthorityInfo authority = mAuthorities.valueAt(i);
- // skip this authority if it isn't one of the renamed ones
- final String newAuthorityName = sAuthorityRenames.get(authority.authority);
- if (newAuthorityName == null) {
- continue;
- }
-
- // remember this authority so we can remove it later. we can't remove it
- // now without messing up this loop iteration
- authoritiesToRemove.add(authority);
-
- // this authority isn't enabled, no need to copy it to the new authority name since
- // the default is "disabled"
- if (!authority.enabled) {
- continue;
- }
-
- // if we already have a record of this new authority then don't copy over the settings
- if (getAuthorityLocked(authority.account, authority.userId, newAuthorityName, "cleanup")
- != null) {
- continue;
- }
-
- AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account,
- authority.userId, newAuthorityName, -1 /* ident */, false /* doWrite */);
- newAuthority.enabled = true;
- writeNeeded = true;
- }
-
- for (AuthorityInfo authorityInfo : authoritiesToRemove) {
- removeAuthorityLocked(authorityInfo.account, authorityInfo.userId,
- authorityInfo.authority, false /* doWrite */);
- writeNeeded = true;
- }
-
- return writeNeeded;
- }
-
- private void parseListenForTickles(XmlPullParser parser) {
- String user = parser.getAttributeValue(null, XML_ATTR_USER);
- int userId = 0;
- try {
- userId = Integer.parseInt(user);
- } catch (NumberFormatException e) {
- Log.e(TAG, "error parsing the user for listen-for-tickles", e);
- } catch (NullPointerException e) {
- Log.e(TAG, "the user in listen-for-tickles is null", e);
- }
- String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
- boolean listen = enabled == null || Boolean.parseBoolean(enabled);
- mMasterSyncAutomatically.put(userId, listen);
- }
-
- private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
- AuthorityInfo authority = null;
- int id = -1;
- try {
- id = Integer.parseInt(parser.getAttributeValue(
- null, "id"));
- } catch (NumberFormatException e) {
- Log.e(TAG, "error parsing the id of the authority", e);
- } catch (NullPointerException e) {
- Log.e(TAG, "the id of the authority is null", e);
- }
- if (id >= 0) {
- String authorityName = parser.getAttributeValue(null, "authority");
- String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
- String syncable = parser.getAttributeValue(null, "syncable");
- String accountName = parser.getAttributeValue(null, "account");
- String accountType = parser.getAttributeValue(null, "type");
- String user = parser.getAttributeValue(null, XML_ATTR_USER);
- int userId = user == null ? 0 : Integer.parseInt(user);
- if (accountType == null) {
- accountType = "com.google";
- syncable = "unknown";
- }
- authority = mAuthorities.get(id);
- if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
- + accountName + " auth=" + authorityName
- + " user=" + userId
- + " enabled=" + enabled
- + " syncable=" + syncable);
- if (authority == null) {
- if (DEBUG_FILE) Log.v(TAG, "Creating entry");
- authority = getOrCreateAuthorityLocked(
- new Account(accountName, accountType), userId, authorityName, id, false);
- // If the version is 0 then we are upgrading from a file format that did not
- // know about periodic syncs. In that case don't clear the list since we
- // want the default, which is a daily periodioc sync.
- // Otherwise clear out this default list since we will populate it later with
- // the periodic sync descriptions that are read from the configuration file.
- if (version > 0) {
- authority.periodicSyncs.clear();
- }
- }
- if (authority != null) {
- authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
- if ("unknown".equals(syncable)) {
- authority.syncable = -1;
- } else {
- authority.syncable =
- (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0;
- }
- } else {
- Log.w(TAG, "Failure adding authority: account="
- + accountName + " auth=" + authorityName
- + " enabled=" + enabled
- + " syncable=" + syncable);
- }
- }
-
- return authority;
- }
-
- private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
- Bundle extras = new Bundle();
- String periodValue = parser.getAttributeValue(null, "period");
- final long period;
- try {
- period = Long.parseLong(periodValue);
- } catch (NumberFormatException e) {
- Log.e(TAG, "error parsing the period of a periodic sync", e);
- return null;
- } catch (NullPointerException e) {
- Log.e(TAG, "the period of a periodic sync is null", e);
- return null;
- }
- final Pair<Bundle, Long> periodicSync = Pair.create(extras, period);
- authority.periodicSyncs.add(periodicSync);
-
- return periodicSync;
- }
-
- private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) {
- final Bundle extras = periodicSync.first;
- String name = parser.getAttributeValue(null, "name");
- String type = parser.getAttributeValue(null, "type");
- String value1 = parser.getAttributeValue(null, "value1");
- String value2 = parser.getAttributeValue(null, "value2");
-
- try {
- if ("long".equals(type)) {
- extras.putLong(name, Long.parseLong(value1));
- } else if ("integer".equals(type)) {
- extras.putInt(name, Integer.parseInt(value1));
- } else if ("double".equals(type)) {
- extras.putDouble(name, Double.parseDouble(value1));
- } else if ("float".equals(type)) {
- extras.putFloat(name, Float.parseFloat(value1));
- } else if ("boolean".equals(type)) {
- extras.putBoolean(name, Boolean.parseBoolean(value1));
- } else if ("string".equals(type)) {
- extras.putString(name, value1);
- } else if ("account".equals(type)) {
- extras.putParcelable(name, new Account(value1, value2));
- }
- } catch (NumberFormatException e) {
- Log.e(TAG, "error parsing bundle value", e);
- } catch (NullPointerException e) {
- Log.e(TAG, "error parsing bundle value", e);
- }
- }
-
- /**
- * Write all account information to the account file.
- */
- private void writeAccountInfoLocked() {
- if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile());
- FileOutputStream fos = null;
-
- try {
- fos = mAccountInfoFile.startWrite();
- XmlSerializer out = new FastXmlSerializer();
- out.setOutput(fos, "utf-8");
- out.startDocument(null, true);
- out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-
- out.startTag(null, "accounts");
- out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
- out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
- out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset));
-
- // Write the Sync Automatically flags for each user
- final int M = mMasterSyncAutomatically.size();
- for (int m = 0; m < M; m++) {
- int userId = mMasterSyncAutomatically.keyAt(m);
- Boolean listen = mMasterSyncAutomatically.valueAt(m);
- out.startTag(null, XML_TAG_LISTEN_FOR_TICKLES);
- out.attribute(null, XML_ATTR_USER, Integer.toString(userId));
- out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(listen));
- out.endTag(null, XML_TAG_LISTEN_FOR_TICKLES);
- }
-
- final int N = mAuthorities.size();
- for (int i=0; i<N; i++) {
- AuthorityInfo authority = mAuthorities.valueAt(i);
- out.startTag(null, "authority");
- out.attribute(null, "id", Integer.toString(authority.ident));
- out.attribute(null, "account", authority.account.name);
- out.attribute(null, XML_ATTR_USER, Integer.toString(authority.userId));
- out.attribute(null, "type", authority.account.type);
- out.attribute(null, "authority", authority.authority);
- out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled));
- if (authority.syncable < 0) {
- out.attribute(null, "syncable", "unknown");
- } else {
- out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0));
- }
- for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) {
- out.startTag(null, "periodicSync");
- out.attribute(null, "period", Long.toString(periodicSync.second));
- final Bundle extras = periodicSync.first;
- for (String key : extras.keySet()) {
- out.startTag(null, "extra");
- out.attribute(null, "name", key);
- final Object value = extras.get(key);
- if (value instanceof Long) {
- out.attribute(null, "type", "long");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Integer) {
- out.attribute(null, "type", "integer");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Boolean) {
- out.attribute(null, "type", "boolean");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Float) {
- out.attribute(null, "type", "float");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Double) {
- out.attribute(null, "type", "double");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof String) {
- out.attribute(null, "type", "string");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Account) {
- out.attribute(null, "type", "account");
- out.attribute(null, "value1", ((Account)value).name);
- out.attribute(null, "value2", ((Account)value).type);
- }
- out.endTag(null, "extra");
- }
- out.endTag(null, "periodicSync");
- }
- out.endTag(null, "authority");
- }
-
- out.endTag(null, "accounts");
-
- out.endDocument();
-
- mAccountInfoFile.finishWrite(fos);
- } catch (java.io.IOException e1) {
- Log.w(TAG, "Error writing accounts", e1);
- if (fos != null) {
- mAccountInfoFile.failWrite(fos);
- }
- }
- }
-
- static int getIntColumn(Cursor c, String name) {
- return c.getInt(c.getColumnIndex(name));
- }
-
- static long getLongColumn(Cursor c, String name) {
- return c.getLong(c.getColumnIndex(name));
- }
-
- /**
- * Load sync engine state from the old syncmanager database, and then
- * erase it. Note that we don't deal with pending operations, active
- * sync, or history.
- */
- private void readAndDeleteLegacyAccountInfoLocked() {
- // Look for old database to initialize from.
- File file = mContext.getDatabasePath("syncmanager.db");
- if (!file.exists()) {
- return;
- }
- String path = file.getPath();
- SQLiteDatabase db = null;
- try {
- db = SQLiteDatabase.openDatabase(path, null,
- SQLiteDatabase.OPEN_READONLY);
- } catch (SQLiteException e) {
- }
-
- if (db != null) {
- final boolean hasType = db.getVersion() >= 11;
-
- // Copy in all of the status information, as well as accounts.
- if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db");
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables("stats, status");
- HashMap<String,String> map = new HashMap<String,String>();
- map.put("_id", "status._id as _id");
- map.put("account", "stats.account as account");
- if (hasType) {
- map.put("account_type", "stats.account_type as account_type");
- }
- map.put("authority", "stats.authority as authority");
- map.put("totalElapsedTime", "totalElapsedTime");
- map.put("numSyncs", "numSyncs");
- map.put("numSourceLocal", "numSourceLocal");
- map.put("numSourcePoll", "numSourcePoll");
- map.put("numSourceServer", "numSourceServer");
- map.put("numSourceUser", "numSourceUser");
- map.put("lastSuccessSource", "lastSuccessSource");
- map.put("lastSuccessTime", "lastSuccessTime");
- map.put("lastFailureSource", "lastFailureSource");
- map.put("lastFailureTime", "lastFailureTime");
- map.put("lastFailureMesg", "lastFailureMesg");
- map.put("pending", "pending");
- qb.setProjectionMap(map);
- qb.appendWhere("stats._id = status.stats_id");
- Cursor c = qb.query(db, null, null, null, null, null, null);
- while (c.moveToNext()) {
- String accountName = c.getString(c.getColumnIndex("account"));
- String accountType = hasType
- ? c.getString(c.getColumnIndex("account_type")) : null;
- if (accountType == null) {
- accountType = "com.google";
- }
- String authorityName = c.getString(c.getColumnIndex("authority"));
- AuthorityInfo authority = this.getOrCreateAuthorityLocked(
- new Account(accountName, accountType), 0 /* legacy is single-user */,
- authorityName, -1, false);
- if (authority != null) {
- int i = mSyncStatus.size();
- boolean found = false;
- SyncStatusInfo st = null;
- while (i > 0) {
- i--;
- st = mSyncStatus.valueAt(i);
- if (st.authorityId == authority.ident) {
- found = true;
- break;
- }
- }
- if (!found) {
- st = new SyncStatusInfo(authority.ident);
- mSyncStatus.put(authority.ident, st);
- }
- st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
- st.numSyncs = getIntColumn(c, "numSyncs");
- st.numSourceLocal = getIntColumn(c, "numSourceLocal");
- st.numSourcePoll = getIntColumn(c, "numSourcePoll");
- st.numSourceServer = getIntColumn(c, "numSourceServer");
- st.numSourceUser = getIntColumn(c, "numSourceUser");
- st.numSourcePeriodic = 0;
- st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
- st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
- st.lastFailureSource = getIntColumn(c, "lastFailureSource");
- st.lastFailureTime = getLongColumn(c, "lastFailureTime");
- st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
- st.pending = getIntColumn(c, "pending") != 0;
- }
- }
-
- c.close();
-
- // Retrieve the settings.
- qb = new SQLiteQueryBuilder();
- qb.setTables("settings");
- c = qb.query(db, null, null, null, null, null, null);
- while (c.moveToNext()) {
- String name = c.getString(c.getColumnIndex("name"));
- String value = c.getString(c.getColumnIndex("value"));
- if (name == null) continue;
- if (name.equals("listen_for_tickles")) {
- setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0);
- } else if (name.startsWith("sync_provider_")) {
- String provider = name.substring("sync_provider_".length(),
- name.length());
- int i = mAuthorities.size();
- while (i > 0) {
- i--;
- AuthorityInfo authority = mAuthorities.valueAt(i);
- if (authority.authority.equals(provider)) {
- authority.enabled = value == null || Boolean.parseBoolean(value);
- authority.syncable = 1;
- }
- }
- }
- }
-
- c.close();
-
- db.close();
-
- (new File(path)).delete();
- }
- }
-
- public static final int STATUS_FILE_END = 0;
- public static final int STATUS_FILE_ITEM = 100;
-
- /**
- * Read all sync status back in to the initial engine state.
- */
- private void readStatusLocked() {
- if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile());
- try {
- byte[] data = mStatusFile.readFully();
- Parcel in = Parcel.obtain();
- in.unmarshall(data, 0, data.length);
- in.setDataPosition(0);
- int token;
- while ((token=in.readInt()) != STATUS_FILE_END) {
- if (token == STATUS_FILE_ITEM) {
- SyncStatusInfo status = new SyncStatusInfo(in);
- if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
- status.pending = false;
- if (DEBUG_FILE) Log.v(TAG, "Adding status for id "
- + status.authorityId);
- mSyncStatus.put(status.authorityId, status);
- }
- } else {
- // Ooops.
- Log.w(TAG, "Unknown status token: " + token);
- break;
- }
- }
- } catch (java.io.IOException e) {
- Log.i(TAG, "No initial status");
- }
- }
-
- /**
- * Write all sync status to the sync status file.
- */
- private void writeStatusLocked() {
- if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile());
-
- // The file is being written, so we don't need to have a scheduled
- // write until the next change.
- removeMessages(MSG_WRITE_STATUS);
-
- FileOutputStream fos = null;
- try {
- fos = mStatusFile.startWrite();
- Parcel out = Parcel.obtain();
- final int N = mSyncStatus.size();
- for (int i=0; i<N; i++) {
- SyncStatusInfo status = mSyncStatus.valueAt(i);
- out.writeInt(STATUS_FILE_ITEM);
- status.writeToParcel(out, 0);
- }
- out.writeInt(STATUS_FILE_END);
- fos.write(out.marshall());
- out.recycle();
-
- mStatusFile.finishWrite(fos);
- } catch (java.io.IOException e1) {
- Log.w(TAG, "Error writing status", e1);
- if (fos != null) {
- mStatusFile.failWrite(fos);
- }
- }
- }
-
- public static final int PENDING_OPERATION_VERSION = 2;
-
- /**
- * Read all pending operations back in to the initial engine state.
- */
- private void readPendingOperationsLocked() {
- if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile());
- try {
- byte[] data = mPendingFile.readFully();
- Parcel in = Parcel.obtain();
- in.unmarshall(data, 0, data.length);
- in.setDataPosition(0);
- final int SIZE = in.dataSize();
- while (in.dataPosition() < SIZE) {
- int version = in.readInt();
- if (version != PENDING_OPERATION_VERSION && version != 1) {
- Log.w(TAG, "Unknown pending operation version "
- + version + "; dropping all ops");
- break;
- }
- int authorityId = in.readInt();
- int syncSource = in.readInt();
- byte[] flatExtras = in.createByteArray();
- boolean expedited;
- if (version == PENDING_OPERATION_VERSION) {
- expedited = in.readInt() != 0;
- } else {
- expedited = false;
- }
- AuthorityInfo authority = mAuthorities.get(authorityId);
- if (authority != null) {
- Bundle extras;
- if (flatExtras != null) {
- extras = unflattenBundle(flatExtras);
- } else {
- // if we are unable to parse the extras for whatever reason convert this
- // to a regular sync by creating an empty extras
- extras = new Bundle();
- }
- PendingOperation op = new PendingOperation(
- authority.account, authority.userId, syncSource,
- authority.authority, extras, expedited);
- op.authorityId = authorityId;
- op.flatExtras = flatExtras;
- if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
- + " auth=" + op.authority
- + " src=" + op.syncSource
- + " expedited=" + op.expedited
- + " extras=" + op.extras);
- mPendingOperations.add(op);
- }
- }
- } catch (java.io.IOException e) {
- Log.i(TAG, "No initial pending operations");
- }
- }
-
- private void writePendingOperationLocked(PendingOperation op, Parcel out) {
- out.writeInt(PENDING_OPERATION_VERSION);
- out.writeInt(op.authorityId);
- out.writeInt(op.syncSource);
- if (op.flatExtras == null && op.extras != null) {
- op.flatExtras = flattenBundle(op.extras);
- }
- out.writeByteArray(op.flatExtras);
- out.writeInt(op.expedited ? 1 : 0);
- }
-
- /**
- * Write all currently pending ops to the pending ops file.
- */
- private void writePendingOperationsLocked() {
- final int N = mPendingOperations.size();
- FileOutputStream fos = null;
- try {
- if (N == 0) {
- if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
- mPendingFile.truncate();
- return;
- }
-
- if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
- fos = mPendingFile.startWrite();
-
- Parcel out = Parcel.obtain();
- for (int i=0; i<N; i++) {
- PendingOperation op = mPendingOperations.get(i);
- writePendingOperationLocked(op, out);
- }
- fos.write(out.marshall());
- out.recycle();
-
- mPendingFile.finishWrite(fos);
- } catch (java.io.IOException e1) {
- Log.w(TAG, "Error writing pending operations", e1);
- if (fos != null) {
- mPendingFile.failWrite(fos);
- }
- }
- }
-
- /**
- * Append the given operation to the pending ops file; if unable to,
- * write all pending ops.
- */
- private void appendPendingOperationLocked(PendingOperation op) {
- if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
- FileOutputStream fos = null;
- try {
- fos = mPendingFile.openAppend();
- } catch (java.io.IOException e) {
- if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file");
- writePendingOperationsLocked();
- return;
- }
-
- try {
- Parcel out = Parcel.obtain();
- writePendingOperationLocked(op, out);
- fos.write(out.marshall());
- out.recycle();
- } catch (java.io.IOException e1) {
- Log.w(TAG, "Error writing pending operations", e1);
- } finally {
- try {
- fos.close();
- } catch (java.io.IOException e2) {
- }
- }
- }
-
- static private byte[] flattenBundle(Bundle bundle) {
- byte[] flatData = null;
- Parcel parcel = Parcel.obtain();
- try {
- bundle.writeToParcel(parcel, 0);
- flatData = parcel.marshall();
- } finally {
- parcel.recycle();
- }
- return flatData;
- }
-
- static private Bundle unflattenBundle(byte[] flatData) {
- Bundle bundle;
- Parcel parcel = Parcel.obtain();
- try {
- parcel.unmarshall(flatData, 0, flatData.length);
- parcel.setDataPosition(0);
- bundle = parcel.readBundle();
- } catch (RuntimeException e) {
- // A RuntimeException is thrown if we were unable to parse the parcel.
- // Create an empty parcel in this case.
- bundle = new Bundle();
- } finally {
- parcel.recycle();
- }
- return bundle;
- }
-
- private void requestSync(Account account, int userId, String authority, Bundle extras) {
- // If this is happening in the system process, then call the syncrequest listener
- // to make a request back to the SyncManager directly.
- // If this is probably a test instance, then call back through the ContentResolver
- // which will know which userId to apply based on the Binder id.
- if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
- && mSyncRequestListener != null) {
- mSyncRequestListener.onSyncRequest(account, userId, authority, extras);
- } else {
- ContentResolver.requestSync(account, authority, extras);
- }
- }
-
- public static final int STATISTICS_FILE_END = 0;
- public static final int STATISTICS_FILE_ITEM_OLD = 100;
- public static final int STATISTICS_FILE_ITEM = 101;
-
- /**
- * Read all sync statistics back in to the initial engine state.
- */
- private void readStatisticsLocked() {
- try {
- byte[] data = mStatisticsFile.readFully();
- Parcel in = Parcel.obtain();
- in.unmarshall(data, 0, data.length);
- in.setDataPosition(0);
- int token;
- int index = 0;
- while ((token=in.readInt()) != STATISTICS_FILE_END) {
- if (token == STATISTICS_FILE_ITEM
- || token == STATISTICS_FILE_ITEM_OLD) {
- int day = in.readInt();
- if (token == STATISTICS_FILE_ITEM_OLD) {
- day = day - 2009 + 14245; // Magic!
- }
- DayStats ds = new DayStats(day);
- ds.successCount = in.readInt();
- ds.successTime = in.readLong();
- ds.failureCount = in.readInt();
- ds.failureTime = in.readLong();
- if (index < mDayStats.length) {
- mDayStats[index] = ds;
- index++;
- }
- } else {
- // Ooops.
- Log.w(TAG, "Unknown stats token: " + token);
- break;
- }
- }
- } catch (java.io.IOException e) {
- Log.i(TAG, "No initial statistics");
- }
- }
-
- /**
- * Write all sync statistics to the sync status file.
- */
- private void writeStatisticsLocked() {
- if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
-
- // The file is being written, so we don't need to have a scheduled
- // write until the next change.
- removeMessages(MSG_WRITE_STATISTICS);
-
- FileOutputStream fos = null;
- try {
- fos = mStatisticsFile.startWrite();
- Parcel out = Parcel.obtain();
- final int N = mDayStats.length;
- for (int i=0; i<N; i++) {
- DayStats ds = mDayStats[i];
- if (ds == null) {
- break;
- }
- out.writeInt(STATISTICS_FILE_ITEM);
- out.writeInt(ds.day);
- out.writeInt(ds.successCount);
- out.writeLong(ds.successTime);
- out.writeInt(ds.failureCount);
- out.writeLong(ds.failureTime);
- }
- out.writeInt(STATISTICS_FILE_END);
- fos.write(out.marshall());
- out.recycle();
-
- mStatisticsFile.finishWrite(fos);
- } catch (java.io.IOException e1) {
- Log.w(TAG, "Error writing stats", e1);
- if (fos != null) {
- mStatisticsFile.failWrite(fos);
- }
- }
- }
-}
diff --git a/core/java/android/content/UriMatcher.java b/core/java/android/content/UriMatcher.java
index 841c8f4..1a8ea47 100644
--- a/core/java/android/content/UriMatcher.java
+++ b/core/java/android/content/UriMatcher.java
@@ -69,6 +69,11 @@ For example:
sURIMatcher.addURI("call_log", "calls/#", CALLS_ID);
}
</pre>
+<p>Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, paths can start
+ with a leading slash. For example:
+<pre>
+ sURIMatcher.addURI("contacts", "/people", PEOPLE);
+</pre>
<p>Then when you need to match against a URI, call {@link #match}, providing
the URL that you have been given. You can use the result to build a query,
return a type, insert or delete a row, or whatever you need, without duplicating
@@ -143,6 +148,9 @@ public class UriMatcher
* matched. URI nodes may be exact match string, the token "*"
* that matches any text, or the token "#" that matches only
* numbers.
+ * <p>
+ * Starting from API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * this method will accept leading slash in the path.
*
* @param authority the authority to match
* @param path the path to match. * may be used as a wild card for
@@ -155,7 +163,17 @@ public class UriMatcher
if (code < 0) {
throw new IllegalArgumentException("code " + code + " is invalid: it must be positive");
}
- String[] tokens = path != null ? PATH_SPLIT_PATTERN.split(path) : null;
+
+ String[] tokens = null;
+ if (path != null) {
+ String newPath = path;
+ // Strip leading slash if present.
+ if (path.length() > 0 && path.charAt(0) == '/') {
+ newPath = path.substring(1);
+ }
+ tokens = PATH_SPLIT_PATTERN.split(newPath);
+ }
+
int numTokens = tokens != null ? tokens.length : 0;
UriMatcher node = this;
for (int i = -1; i < numTokens; i++) {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 8f3b62d..8154bca 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -279,6 +279,30 @@ public class ActivityInfo extends ComponentInfo
public static final int SCREEN_ORIENTATION_FULL_SENSOR = 10;
/**
+ * Constant corresponding to <code>userLandscape</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_USER_LANDSCAPE = 11;
+
+ /**
+ * Constant corresponding to <code>userPortrait</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_USER_PORTRAIT = 12;
+
+ /**
+ * Constant corresponding to <code>fullUser</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_FULL_USER = 13;
+
+ /**
+ * Constant corresponding to <code>locked</code> in
+ * the {@link android.R.attr#screenOrientation} attribute.
+ */
+ public static final int SCREEN_ORIENTATION_LOCKED = 14;
+
+ /**
* The preferred screen orientation this activity would like to run in.
* From the {@link android.R.attr#screenOrientation} attribute, one of
* {@link #SCREEN_ORIENTATION_UNSPECIFIED},
@@ -292,7 +316,11 @@ public class ActivityInfo extends ComponentInfo
* {@link #SCREEN_ORIENTATION_SENSOR_PORTRAIT},
* {@link #SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
* {@link #SCREEN_ORIENTATION_REVERSE_PORTRAIT},
- * {@link #SCREEN_ORIENTATION_FULL_SENSOR}.
+ * {@link #SCREEN_ORIENTATION_FULL_SENSOR},
+ * {@link #SCREEN_ORIENTATION_USER_LANDSCAPE},
+ * {@link #SCREEN_ORIENTATION_USER_PORTRAIT},
+ * {@link #SCREEN_ORIENTATION_FULL_USER},
+ * {@link #SCREEN_ORIENTATION_LOCKED},
*/
public int screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
@@ -398,7 +426,7 @@ public class ActivityInfo extends ComponentInfo
* 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
+ * not a core resource configuration, but a higher-level value, so its
* constant starts at the high bits.
*/
public static final int CONFIG_FONT_SCALE = 0x40000000;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index b9e432c..a0e1555 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -127,7 +127,16 @@ interface IPackageManager {
* limit that kicks in when flags are included that bloat up the data
* returned.
*/
- ParceledListSlice getInstalledPackages(int flags, in String lastRead, in int userId);
+ ParceledListSlice getInstalledPackages(int flags, in int userId);
+
+ /**
+ * This implements getPackagesHoldingPermissions via a "last returned row"
+ * mechanism that is not exposed in the API. This is to get around the IPC
+ * limit that kicks in when flags are included that bloat up the data
+ * returned.
+ */
+ ParceledListSlice getPackagesHoldingPermissions(in String[] permissions,
+ int flags, int userId);
/**
* This implements getInstalledApplications via a "last returned row"
@@ -135,7 +144,7 @@ interface IPackageManager {
* limit that kicks in when flags are included that bloat up the data
* returned.
*/
- ParceledListSlice getInstalledApplications(int flags, in String lastRead, int userId);
+ ParceledListSlice getInstalledApplications(int flags, int userId);
/**
* Retrieve all applications that are marked as persistent.
@@ -185,22 +194,26 @@ interface IPackageManager {
void setInstallerPackageName(in String targetPackage, in String installerPackageName);
/**
- * Delete a package.
+ * Delete a package for a specific user.
*
* @param packageName The fully qualified name of the package to delete.
* @param observer a callback to use to notify when the package deletion in finished.
+ * @param userId the id of the user for whom to delete the package
* @param flags - possible values: {@link #DONT_DELETE_DATA}
*/
- void deletePackage(in String packageName, IPackageDeleteObserver observer, int flags);
+ void deletePackageAsUser(in String packageName, IPackageDeleteObserver observer,
+ int userId, int flags);
String getInstallerPackageName(in String packageName);
void addPackageToPreferred(String packageName);
-
+
void removePackageFromPreferred(String packageName);
-
+
List<PackageInfo> getPreferredPackages(int flags);
+ void resetPreferredActivities(int userId);
+
void addPreferredActivity(in IntentFilter filter, int match,
in ComponentName[] set, in ComponentName activity, int userId);
@@ -226,7 +239,8 @@ interface IPackageManager {
/**
* As per {@link android.content.pm.PackageManager#setApplicationEnabledSetting}.
*/
- void setApplicationEnabledSetting(in String packageName, in int newState, int flags, int userId);
+ void setApplicationEnabledSetting(in String packageName, in int newState, int flags,
+ int userId, String callingPackage);
/**
* As per {@link android.content.pm.PackageManager#getApplicationEnabledSetting}.
@@ -370,7 +384,7 @@ interface IPackageManager {
in VerificationParams verificationParams,
in ContainerEncryptionParams encryptionParams);
- int installExistingPackage(String packageName);
+ int installExistingPackageAsUser(String packageName, int userId);
void verifyPendingInstall(int id, int verificationCode);
void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay);
diff --git a/core/java/android/content/pm/ManifestDigest.java b/core/java/android/content/pm/ManifestDigest.java
index 75505bc..409b5ae 100644
--- a/core/java/android/content/pm/ManifestDigest.java
+++ b/core/java/android/content/pm/ManifestDigest.java
@@ -18,10 +18,17 @@ package android.content.pm;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Base64;
-
+import android.util.Slog;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
-import java.util.jar.Attributes;
+
+import libcore.io.IoUtils;
/**
* Represents the manifest digest for a package. This is suitable for comparison
@@ -30,17 +37,17 @@ import java.util.jar.Attributes;
* @hide
*/
public class ManifestDigest implements Parcelable {
+ private static final String TAG = "ManifestDigest";
+
/** The digest of the manifest in our preferred order. */
private final byte[] mDigest;
- /** Digest field names to look for in preferred order. */
- private static final String[] DIGEST_TYPES = {
- "SHA1-Digest", "SHA-Digest", "MD5-Digest",
- };
-
/** What we print out first when toString() is called. */
private static final String TO_STRING_PREFIX = "ManifestDigest {mDigest=";
+ /** Digest algorithm to use. */
+ private static final String DIGEST_ALGORITHM = "SHA-256";
+
ManifestDigest(byte[] digest) {
mDigest = digest;
}
@@ -49,26 +56,32 @@ public class ManifestDigest implements Parcelable {
mDigest = source.createByteArray();
}
- static ManifestDigest fromAttributes(Attributes attributes) {
- if (attributes == null) {
+ static ManifestDigest fromInputStream(InputStream fileIs) {
+ if (fileIs == null) {
return null;
}
- String encodedDigest = null;
-
- for (int i = 0; i < DIGEST_TYPES.length; i++) {
- final String value = attributes.getValue(DIGEST_TYPES[i]);
- if (value != null) {
- encodedDigest = value;
- break;
- }
+ final MessageDigest md;
+ try {
+ md = MessageDigest.getInstance(DIGEST_ALGORITHM);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException(DIGEST_ALGORITHM + " must be available", e);
}
- if (encodedDigest == null) {
+ final DigestInputStream dis = new DigestInputStream(new BufferedInputStream(fileIs), md);
+ try {
+ byte[] readBuffer = new byte[8192];
+ while (dis.read(readBuffer, 0, readBuffer.length) != -1) {
+ // not using
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "Could not read manifest");
return null;
+ } finally {
+ IoUtils.closeQuietly(dis);
}
- final byte[] digest = Base64.decode(encodedDigest, Base64.DEFAULT);
+ final byte[] digest = md.digest();
return new ManifestDigest(digest);
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 85f7aa5..af1a6d5 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -217,7 +217,16 @@ public class PackageInfo implements Parcelable {
* @hide
*/
public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY;
-
+
+ /** @hide */
+ public boolean requiredForAllUsers;
+
+ /** @hide */
+ public String restrictedAccountType;
+
+ /** @hide */
+ public String requiredAccountType;
+
public PackageInfo() {
}
@@ -258,6 +267,9 @@ public class PackageInfo implements Parcelable {
dest.writeTypedArray(configPreferences, parcelableFlags);
dest.writeTypedArray(reqFeatures, parcelableFlags);
dest.writeInt(installLocation);
+ dest.writeInt(requiredForAllUsers ? 1 : 0);
+ dest.writeString(restrictedAccountType);
+ dest.writeString(requiredAccountType);
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -296,5 +308,8 @@ public class PackageInfo implements Parcelable {
configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
reqFeatures = source.createTypedArray(FeatureInfo.CREATOR);
installLocation = source.readInt();
+ requiredForAllUsers = source.readInt() != 0;
+ restrictedAccountType = source.readString();
+ requiredAccountType = source.readString();
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 2c31ea0..4266d85 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -175,6 +175,14 @@ public abstract class PackageManager {
public static final int GET_CONFIGURATIONS = 0x00004000;
/**
+ * {@link PackageInfo} flag: include disabled components which are in
+ * that state only because of {@link #COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED}
+ * in the returned info. Note that if you set this flag, applications
+ * that are in this disabled state will be reported as enabled.
+ */
+ public static final int GET_DISABLED_UNTIL_USED_COMPONENTS = 0x00008000;
+
+ /**
* Resolution and querying flag: if set, only filters that support the
* {@link android.content.Intent#CATEGORY_DEFAULT} will be considered for
* matching. This is a synonym for including the CATEGORY_DEFAULT in your
@@ -265,6 +273,19 @@ public abstract class PackageManager {
public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3;
/**
+ * Flag for {@link #setApplicationEnabledSetting(String, int, int)} only: This
+ * application should be considered, until the point where the user actually
+ * wants to use it. This means that it will not normally show up to the user
+ * (such as in the launcher), but various parts of the user interface can
+ * use {@link #GET_DISABLED_UNTIL_USED_COMPONENTS} to still see it and allow
+ * the user to select it (as for example an IME, device admin, etc). Such code,
+ * once the user has selected the app, should at that point also make it enabled.
+ * This option currently <strong>can not</strong> be used with
+ * {@link #setComponentEnabledSetting(ComponentName, int, int)}.
+ */
+ public static final int COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4;
+
+ /**
* Flag parameter for {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} to
* indicate that this package should be installed as forward locked, i.e. only the app itself
* should have access to its code and non-resource assets.
@@ -645,6 +666,15 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
/**
+ * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the system failed to install the package because the user is restricted from installing
+ * apps.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_USER_RESTRICTED = -111;
+
+ /**
* Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
* package's data directory.
*
@@ -661,6 +691,17 @@ public abstract class PackageManager {
public static final int DELETE_ALL_USERS = 0x00000002;
/**
+ * Flag parameter for {@link #deletePackage} to indicate that, if you are calling
+ * uninstall on a system that has been updated, then don't do the normal process
+ * of uninstalling the update and rolling back to the older system version (which
+ * needs to happen for all users); instead, just mark the app as uninstalled for
+ * the current user.
+ *
+ * @hide
+ */
+ public static final int DELETE_SYSTEM_APP = 0x00000004;
+
+ /**
* Return code for when package deletion succeeds. This is passed to the
* {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
* succeeded in deleting the package.
@@ -689,6 +730,15 @@ public abstract class PackageManager {
public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
/**
+ * Deletion failed return code: this is passed to the
+ * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
+ * failed to delete the package since the user is restricted.
+ *
+ * @hide
+ */
+ public static final int DELETE_FAILED_USER_RESTRICTED = -3;
+
+ /**
* Return code that is passed to the {@link IPackageMoveObserver} by
* {@link #movePackage(android.net.Uri, IPackageMoveObserver)} when the
* package has been successfully moved by the system.
@@ -820,6 +870,14 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device is capable of communicating with
+ * other devices via Bluetooth Low Energy radio.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device has a camera facing away
* from the screen.
*/
@@ -1095,6 +1153,29 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports app widgets.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_APP_WIDGETS = "android.software.app_widgets";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports a home screen that is replaceable
+ * by third party applications.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_HOME_SCREEN = "android.software.home_screen";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports adding new input methods implemented
+ * with the {@link android.inputmethodservice.InputMethodService} API.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports WiFi (802.11) networking.
*/
@SdkConstant(SdkConstantType.FEATURE)
@@ -1191,6 +1272,23 @@ public abstract class PackageManager {
= "android.content.pm.extra.VERIFICATION_VERSION_CODE";
/**
+ * The action used to request that the user approve a permission request
+ * from the application.
+ *
+ * @hide
+ */
+ public static final String ACTION_REQUEST_PERMISSION
+ = "android.content.pm.action.REQUEST_PERMISSION";
+
+ /**
+ * Extra field name for the list of permissions, which the user must approve.
+ *
+ * @hide
+ */
+ public static final String EXTRA_REQUEST_PERMISSION_PERMISSION_LIST
+ = "android.content.pm.extra.PERMISSION_LIST";
+
+ /**
* Retrieve overall information about an application package that is
* installed on the system.
* <p>
@@ -1280,6 +1378,22 @@ public abstract class PackageManager {
throws NameNotFoundException;
/**
+ * @hide Return the uid associated with the given package name for the
+ * given user.
+ *
+ * <p>Throws {@link NameNotFoundException} if a package with the given
+ * name can not be found on the system.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of the
+ * desired package.
+ * @param userHandle The user handle identifier to look up the package under.
+ *
+ * @return Returns an integer uid who owns the given package name.
+ */
+ public abstract int getPackageUid(String packageName, int userHandle)
+ throws NameNotFoundException;
+
+ /**
* Retrieve all of the information we know about a particular permission.
*
* <p>Throws {@link NameNotFoundException} if a permission with the given
@@ -1496,11 +1610,43 @@ public abstract class PackageManager {
* @see #GET_SERVICES
* @see #GET_SIGNATURES
* @see #GET_UNINSTALLED_PACKAGES
- *
*/
public abstract List<PackageInfo> getInstalledPackages(int flags);
/**
+ * Return a List of all installed packages that are currently
+ * holding any of the given permissions.
+ *
+ * @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.
+ *
+ * @return Returns a List of PackageInfo objects, one for each installed
+ * application that is holding any of the permissions that were provided.
+ *
+ * @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
+ */
+ public abstract List<PackageInfo> getPackagesHoldingPermissions(
+ String[] permissions, 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.
@@ -1614,6 +1760,30 @@ public abstract class PackageManager {
public abstract void removePermission(String name);
/**
+ * Returns an {@link Intent} suitable for passing to {@code startActivityForResult}
+ * which prompts the user to grant {@code permissions} to this application.
+ * @hide
+ *
+ * @throws NullPointerException if {@code permissions} is {@code null}.
+ * @throws IllegalArgumentException if {@code permissions} contains {@code null}.
+ */
+ public Intent buildPermissionRequestIntent(String... permissions) {
+ if (permissions == null) {
+ throw new NullPointerException("permissions cannot be null");
+ }
+ for (String permission : permissions) {
+ if (permission == null) {
+ throw new IllegalArgumentException("permissions cannot contain null");
+ }
+ }
+
+ Intent i = new Intent(ACTION_REQUEST_PERMISSION);
+ i.putExtra(EXTRA_REQUEST_PERMISSION_PERMISSION_LIST, permissions);
+ i.setPackage("com.android.packageinstaller");
+ return i;
+ }
+
+ /**
* Grant a permission to an application which the application does not
* already have. The permission must have been requested by the application,
* but as an optional permission. If the application is not allowed to
@@ -1733,7 +1903,7 @@ public abstract class PackageManager {
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
*
- * @return A List of ApplicationInfo objects, one for each application that
+ * @return Returns a List of ApplicationInfo objects, one for each application that
* is installed on the device. In the unlikely case of there being
* no installed applications, an empty list is returned.
* If flag GET_UNINSTALLED_PACKAGES is set, a list of all
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5bcf64d..e8a2fd2 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -24,7 +24,6 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
-import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.PatternMatcher;
@@ -54,10 +53,9 @@ import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
-import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
import com.android.internal.util.XmlUtils;
@@ -289,6 +287,12 @@ public class PackageParser {
pi.sharedUserLabel = p.mSharedUserLabel;
pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
pi.installLocation = p.installLocation;
+ if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0
+ || (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
+ pi.requiredForAllUsers = p.mRequiredForAllUsers;
+ }
+ pi.restrictedAccountType = p.mRestrictedAccountType;
+ pi.requiredAccountType = p.mRequiredAccountType;
pi.firstInstallTime = firstInstallTime;
pi.lastUpdateTime = lastUpdateTime;
if ((flags&PackageManager.GET_GIDS) != 0) {
@@ -565,6 +569,28 @@ public class PackageParser {
return pkg;
}
+ /**
+ * Gathers the {@link ManifestDigest} for {@code pkg} if it exists in the
+ * APK. If it successfully scanned the package and found the
+ * {@code AndroidManifest.xml}, {@code true} is returned.
+ */
+ public boolean collectManifestDigest(Package pkg) {
+ try {
+ final JarFile jarFile = new JarFile(mArchiveSourcePath);
+ try {
+ final ZipEntry je = jarFile.getEntry(ANDROID_MANIFEST_FILENAME);
+ if (je != null) {
+ pkg.manifestDigest = ManifestDigest.fromInputStream(jarFile.getInputStream(je));
+ }
+ } finally {
+ jarFile.close();
+ }
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
public boolean collectCertificates(Package pkg, int flags) {
pkg.mSignatures = null;
@@ -616,7 +642,6 @@ public class PackageParser {
}
} else {
Enumeration<JarEntry> entries = jarFile.entries();
- final Manifest manifest = jarFile.getManifest();
while (entries.hasMoreElements()) {
final JarEntry je = entries.nextElement();
if (je.isDirectory()) continue;
@@ -627,8 +652,8 @@ public class PackageParser {
continue;
if (ANDROID_MANIFEST_FILENAME.equals(name)) {
- final Attributes attributes = manifest.getAttributes(name);
- pkg.manifestDigest = ManifestDigest.fromAttributes(attributes);
+ pkg.manifestDigest =
+ ManifestDigest.fromInputStream(jarFile.getInputStream(je));
}
final Certificate[] localCerts = loadCertificates(jarFile, je, readBuffer);
@@ -1015,27 +1040,10 @@ public class PackageParser {
return null;
}
} else if (tagName.equals("uses-permission")) {
- sa = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.AndroidManifestUsesPermission);
-
- // Note: don't allow this value to be a reference to a resource
- // that may change.
- String name = sa.getNonResourceString(
- com.android.internal.R.styleable.AndroidManifestUsesPermission_name);
- /* Not supporting optional permissions yet.
- boolean required = sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestUsesPermission_required, true);
- */
-
- sa.recycle();
-
- if (name != null && !pkg.requestedPermissions.contains(name)) {
- pkg.requestedPermissions.add(name.intern());
- pkg.requestedPermissionsRequired.add(Boolean.TRUE);
+ if (!parseUsesPermission(pkg, res, parser, attrs, outError)) {
+ return null;
}
- XmlUtils.skipCurrentTag(parser);
-
} else if (tagName.equals("uses-configuration")) {
ConfigurationInfo cPref = new ConfigurationInfo();
sa = res.obtainAttributes(attrs,
@@ -1379,9 +1387,56 @@ public class PackageParser {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES;
}
+ /*
+ * b/8528162: Ignore the <uses-permission android:required> attribute if
+ * targetSdkVersion < JELLY_BEAN_MR2. There are lots of apps in the wild
+ * which are improperly using this attribute, even though it never worked.
+ */
+ if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ for (int i = 0; i < pkg.requestedPermissionsRequired.size(); i++) {
+ pkg.requestedPermissionsRequired.set(i, Boolean.TRUE);
+ }
+ }
+
return pkg;
}
+ private boolean parseUsesPermission(Package pkg, Resources res, XmlResourceParser parser,
+ AttributeSet attrs, String[] outError)
+ throws XmlPullParserException, IOException {
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestUsesPermission);
+
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String name = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_name);
+/*
+ boolean required = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestUsesPermission_required, true);
+*/
+ boolean required = true; // Optional <uses-permission> not supported
+
+ sa.recycle();
+
+ if (name != null) {
+ int index = pkg.requestedPermissions.indexOf(name);
+ if (index == -1) {
+ pkg.requestedPermissions.add(name.intern());
+ pkg.requestedPermissionsRequired.add(required ? Boolean.TRUE : Boolean.FALSE);
+ } else {
+ if (pkg.requestedPermissionsRequired.get(index) != required) {
+ outError[0] = "conflicting <uses-permission> entries";
+ mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
+ return false;
+ }
+ }
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+ return true;
+ }
+
private static String buildClassName(String pkg, CharSequence clsSeq,
String[] outError) {
if (clsSeq == null || clsSeq.length() <= 0) {
@@ -1763,6 +1818,24 @@ public class PackageParser {
}
if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_requiredForAllUsers,
+ false)) {
+ owner.mRequiredForAllUsers = true;
+ }
+
+ String restrictedAccountType = sa.getString(com.android.internal.R.styleable
+ .AndroidManifestApplication_restrictedAccountType);
+ if (restrictedAccountType != null && restrictedAccountType.length() > 0) {
+ owner.mRestrictedAccountType = restrictedAccountType;
+ }
+
+ String requiredAccountType = sa.getString(com.android.internal.R.styleable
+ .AndroidManifestApplication_requiredAccountType);
+ if (requiredAccountType != null && requiredAccountType.length() > 0) {
+ owner.mRequiredAccountType = requiredAccountType;
+ }
+
+ if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_debuggable,
false)) {
ai.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
@@ -1941,6 +2014,28 @@ public class PackageParser {
return false;
}
+ } else if (tagName.equals("library")) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestLibrary);
+
+ // Note: don't allow this value to be a reference to a resource
+ // that may change.
+ String lname = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestLibrary_name);
+
+ sa.recycle();
+
+ if (lname != null) {
+ if (owner.libraryNames == null) {
+ owner.libraryNames = new ArrayList<String>();
+ }
+ if (!owner.libraryNames.contains(lname)) {
+ owner.libraryNames.add(lname.intern());
+ }
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
} else if (tagName.equals("uses-library")) {
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestUsesLibrary);
@@ -3165,6 +3260,7 @@ public class PackageParser {
}
public final static class Package {
+
public String packageName;
// For now we only support one application per package.
@@ -3182,7 +3278,8 @@ public class PackageParser {
public final ArrayList<Boolean> requestedPermissionsRequired = new ArrayList<Boolean>();
public ArrayList<String> protectedBroadcasts;
-
+
+ public ArrayList<String> libraryNames = null;
public ArrayList<String> usesLibraries = null;
public ArrayList<String> usesOptionalLibraries = null;
public String[] usesLibraryFiles = null;
@@ -3248,6 +3345,15 @@ public class PackageParser {
public int installLocation;
+ /* An app that's required for all users and cannot be uninstalled for a user */
+ public boolean mRequiredForAllUsers;
+
+ /* The restricted account authenticator type that is used by this application */
+ public String mRestrictedAccountType;
+
+ /* The required account type without which this application will not function */
+ public String mRequiredAccountType;
+
/**
* Digest suitable for comparing whether this package's manifest is the
* same as another.
@@ -3527,29 +3633,45 @@ public class PackageParser {
return generateApplicationInfo(p, flags, state, UserHandle.getCallingUserId());
}
+ private static void updateApplicationInfo(ApplicationInfo ai, int flags,
+ PackageUserState state) {
+ // CompatibilityMode is global state.
+ if (!sCompatibilityModeEnabled) {
+ ai.disableCompatibilityMode();
+ }
+ 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 (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ ai.enabled = (flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != 0;
+ } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+ ai.enabled = false;
+ }
+ ai.enabledSetting = state.enabled;
+ }
+
public static ApplicationInfo generateApplicationInfo(Package p, int flags,
PackageUserState state, int userId) {
if (p == null) return null;
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();
- }
- // 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 (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
- || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
- p.applicationInfo.enabled = false;
- }
- p.applicationInfo.enabledSetting = state.enabled;
+ if (!copyNeeded(flags, p, state, null, userId)
+ && ((flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) == 0
+ || state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
+ // In this case it is safe to directly modify the internal ApplicationInfo state:
+ // - CompatibilityMode is global state, so will be the same for every call.
+ // - We only come in to here if the app should reported as installed; this is the
+ // default state, and we will do a copy otherwise.
+ // - The enable state will always be reported the same for the application across
+ // calls; the only exception is for the UNTIL_USED mode, and in that case we will
+ // be doing a copy.
+ updateApplicationInfo(p.applicationInfo, flags, state);
return p.applicationInfo;
}
@@ -3565,26 +3687,12 @@ public class PackageParser {
if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) {
ai.sharedLibraryFiles = p.usesLibraryFiles;
}
- if (!sCompatibilityModeEnabled) {
- ai.disableCompatibilityMode();
- }
if (state.stopped) {
ai.flags |= ApplicationInfo.FLAG_STOPPED;
} else {
ai.flags &= ~ApplicationInfo.FLAG_STOPPED;
}
- 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 (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
- || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
- ai.enabled = false;
- }
- ai.enabledSetting = state.enabled;
+ updateApplicationInfo(ai, flags, state);
return ai;
}
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 3579977..dcd54fc 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -30,6 +30,8 @@ public class PackageUserState {
public boolean installed;
public int enabled;
+ public String lastDisableAppCaller;
+
public HashSet<String> disabledComponents;
public HashSet<String> enabledComponents;
@@ -43,6 +45,7 @@ public class PackageUserState {
stopped = o.stopped;
notLaunched = o.notLaunched;
enabled = o.enabled;
+ lastDisableAppCaller = o.lastDisableAppCaller;
disabledComponents = o.disabledComponents != null
? new HashSet<String>(o.disabledComponents) : null;
enabledComponents = o.enabledComponents != null
diff --git a/core/java/android/content/pm/ParceledListSlice.java b/core/java/android/content/pm/ParceledListSlice.java
index f3a98db..8a43472 100644
--- a/core/java/android/content/pm/ParceledListSlice.java
+++ b/core/java/android/content/pm/ParceledListSlice.java
@@ -16,44 +16,92 @@
package android.content.pm;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import java.util.ArrayList;
import java.util.List;
/**
- * Builds up a parcel that is discarded when written to another parcel or
- * written to a list. This is useful for API that sends huge lists across a
- * Binder that may be larger than the IPC limit.
+ * Transfer a large list of Parcelable objects across an IPC. Splits into
+ * multiple transactions if needed.
*
* @hide
*/
public class ParceledListSlice<T extends Parcelable> implements Parcelable {
+ private static String TAG = "ParceledListSlice";
+ private static boolean DEBUG = false;
+
/*
* TODO get this number from somewhere else. For now set it to a quarter of
* the 1MB limit.
*/
private static final int MAX_IPC_SIZE = 256 * 1024;
+ private static final int MAX_FIRST_IPC_SIZE = MAX_IPC_SIZE / 2;
- private Parcel mParcel;
-
- private int mNumItems;
+ private final List<T> mList;
- private boolean mIsLastSlice;
+ public ParceledListSlice(List<T> list) {
+ mList = list;
+ }
- public ParceledListSlice() {
- mParcel = Parcel.obtain();
+ private ParceledListSlice(Parcel p, ClassLoader loader) {
+ final int N = p.readInt();
+ mList = new ArrayList<T>(N);
+ if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
+ if (N <= 0) {
+ return;
+ }
+ Parcelable.Creator<T> creator = p.readParcelableCreator(loader);
+ int i = 0;
+ while (i < N) {
+ if (p.readInt() == 0) {
+ break;
+ }
+ mList.add(p.readCreator(creator, loader));
+ if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
+ i++;
+ }
+ if (i >= N) {
+ return;
+ }
+ final IBinder retriever = p.readStrongBinder();
+ while (i < N) {
+ if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInt(i);
+ try {
+ retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e);
+ return;
+ }
+ while (i < N && reply.readInt() != 0) {
+ mList.add(reply.readCreator(creator, loader));
+ if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
+ i++;
+ }
+ reply.recycle();
+ data.recycle();
+ }
}
- private ParceledListSlice(Parcel p, int numItems, boolean lastSlice) {
- mParcel = p;
- mNumItems = numItems;
- mIsLastSlice = lastSlice;
+ public List<T> getList() {
+ return mList;
}
@Override
public int describeContents() {
- return 0;
+ int contents = 0;
+ for (int i=0; i<mList.size(); i++) {
+ contents |= mList.get(i).describeContents();
+ }
+ return contents;
}
/**
@@ -63,104 +111,59 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable {
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mNumItems);
- dest.writeInt(mIsLastSlice ? 1 : 0);
-
- if (mNumItems > 0) {
- final int parcelSize = mParcel.dataSize();
- dest.writeInt(parcelSize);
- dest.appendFrom(mParcel, 0, parcelSize);
- }
-
- mNumItems = 0;
- mParcel.recycle();
- mParcel = null;
- }
-
- /**
- * Appends a parcel to this list slice.
- *
- * @param item Parcelable item to append to this list slice
- * @return true when the list slice is full and should not be appended to
- * anymore
- */
- public boolean append(T item) {
- if (mParcel == null) {
- throw new IllegalStateException("ParceledListSlice has already been recycled");
- }
-
- item.writeToParcel(mParcel, PARCELABLE_WRITE_RETURN_VALUE);
- mNumItems++;
-
- return mParcel.dataSize() > MAX_IPC_SIZE;
- }
-
- /**
- * Populates a list and discards the internal state of the
- * ParceledListSlice in the process. The instance should
- * not be used anymore.
- *
- * @param list list to insert items from this slice.
- * @param creator creator that knows how to unparcel the
- * target object type.
- * @return the last item inserted into the list or null if none.
- */
- public T populateList(List<T> list, Creator<T> creator) {
- mParcel.setDataPosition(0);
-
- T item = null;
- for (int i = 0; i < mNumItems; i++) {
- item = creator.createFromParcel(mParcel);
- list.add(item);
+ final int N = mList.size();
+ final int callFlags = flags;
+ dest.writeInt(N);
+ if (DEBUG) Log.d(TAG, "Writing " + N + " items");
+ if (N > 0) {
+ dest.writeParcelableCreator(mList.get(0));
+ int i = 0;
+ while (i < N && dest.dataSize() < MAX_FIRST_IPC_SIZE) {
+ dest.writeInt(1);
+ mList.get(i).writeToParcel(dest, callFlags);
+ if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
+ i++;
+ }
+ if (i < N) {
+ dest.writeInt(0);
+ Binder retriever = new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ if (code != FIRST_CALL_TRANSACTION) {
+ return super.onTransact(code, data, reply, flags);
+ }
+ int i = data.readInt();
+ if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
+ while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
+ reply.writeInt(1);
+ mList.get(i).writeToParcel(reply, callFlags);
+ if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
+ i++;
+ }
+ if (i < N) {
+ if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
+ reply.writeInt(0);
+ }
+ return true;
+ }
+ };
+ if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
+ dest.writeStrongBinder(retriever);
+ }
}
-
- mParcel.recycle();
- mParcel = null;
-
- return item;
- }
-
- /**
- * Sets whether this is the last list slice in the series.
- *
- * @param lastSlice
- */
- public void setLastSlice(boolean lastSlice) {
- mIsLastSlice = lastSlice;
- }
-
- /**
- * Returns whether this is the last slice in a series of slices.
- *
- * @return true if this is the last slice in the series.
- */
- public boolean isLastSlice() {
- return mIsLastSlice;
}
@SuppressWarnings("unchecked")
- public static final Parcelable.Creator<ParceledListSlice> CREATOR =
- new Parcelable.Creator<ParceledListSlice>() {
+ public static final Parcelable.ClassLoaderCreator<ParceledListSlice> CREATOR =
+ new Parcelable.ClassLoaderCreator<ParceledListSlice>() {
public ParceledListSlice createFromParcel(Parcel in) {
- final int numItems = in.readInt();
- final boolean lastSlice = in.readInt() == 1;
-
- if (numItems > 0) {
- final int parcelSize = in.readInt();
-
- // Advance within this Parcel
- int offset = in.dataPosition();
- in.setDataPosition(offset + parcelSize);
-
- Parcel p = Parcel.obtain();
- p.setDataPosition(0);
- p.appendFrom(in, offset, parcelSize);
- p.setDataPosition(0);
+ return new ParceledListSlice(in, null);
+ }
- return new ParceledListSlice(p, numItems, lastSlice);
- } else {
- return new ParceledListSlice();
- }
+ @Override
+ public ParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
+ return new ParceledListSlice(in, loader);
}
public ParceledListSlice[] newArray(int size) {
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index aaa0917..288d55f 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -488,7 +488,8 @@ public abstract class RegisteredServicesCache<V> {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, null);
int eventType = parser.getEventType();
- while (eventType != XmlPullParser.START_TAG) {
+ while (eventType != XmlPullParser.START_TAG
+ && eventType != XmlPullParser.END_DOCUMENT) {
eventType = parser.next();
}
String tagName = parser.getName();
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index 9c9340d..752bf8b 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -19,6 +19,8 @@ package android.content.pm;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.ArrayUtils;
+
import java.io.ByteArrayInputStream;
import java.lang.ref.SoftReference;
import java.security.PublicKey;
@@ -198,4 +200,13 @@ public class Signature implements Parcelable {
private Signature(Parcel source) {
mSignature = source.createByteArray();
}
+
+ /**
+ * Test if given {@link Signature} sets are exactly equal.
+ *
+ * @hide
+ */
+ public static boolean areExactMatch(Signature[] a, Signature[] b) {
+ return ArrayUtils.containsAll(a, b) && ArrayUtils.containsAll(b, a);
+ }
}
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 593f826..4c87830 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -97,6 +97,10 @@ public class UserInfo implements Parcelable {
return (flags & FLAG_GUEST) == FLAG_GUEST;
}
+ public boolean isRestricted() {
+ return (flags & FLAG_RESTRICTED) == FLAG_RESTRICTED;
+ }
+
public UserInfo() {
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index ffefaa2..fc9e486 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -17,6 +17,7 @@
package android.content.res;
import android.os.ParcelFileDescriptor;
+import android.os.Trace;
import android.util.Log;
import android.util.TypedValue;
@@ -602,7 +603,12 @@ public final class AssetManager {
* the cookie of the added asset, or 0 on failure.
* {@hide}
*/
- public native final int addAssetPath(String path);
+ public final int addAssetPath(String path) {
+ int res = addAssetPathNative(path);
+ return res;
+ }
+
+ private native final int addAssetPathNative(String path);
/**
* Add multiple sets of assets to the asset manager at once. See
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 86d6ee7..905ae0d 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1219,12 +1219,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* Return the layout direction. Will be either {@link View#LAYOUT_DIRECTION_LTR} or
* {@link View#LAYOUT_DIRECTION_RTL}.
*
- * @return the layout direction
+ * @return Returns {@link View#LAYOUT_DIRECTION_RTL} if the configuration
+ * is {@link #SCREENLAYOUT_LAYOUTDIR_RTL}, otherwise {@link View#LAYOUT_DIRECTION_LTR}.
*/
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;
+ return (screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK) == SCREENLAYOUT_LAYOUTDIR_RTL
+ ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
}
/**
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 93a4f77..03b4901 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -16,6 +16,8 @@
package android.content.res;
+import android.os.Trace;
+import android.view.View;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -77,32 +79,33 @@ public class Resources {
private static final int ID_OTHER = 0x01000004;
- private static final Object mSync = new Object();
+ private static final Object sSync = new Object();
/*package*/ static Resources mSystem = null;
-
+
// Information about preloaded resources. Note that they are not
// protected by a lock, because while preloading in zygote we are all
// single-threaded, and after that these are immutable.
- private static final LongSparseArray<Drawable.ConstantState> sPreloadedDrawables
+ private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
+ private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
= new LongSparseArray<Drawable.ConstantState>();
private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists
= new LongSparseArray<ColorStateList>();
- private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
- = new LongSparseArray<Drawable.ConstantState>();
+
private static boolean sPreloaded;
private static int sPreloadedDensity;
- /*package*/ final TypedValue mTmpValue = new TypedValue();
- /*package*/ final Configuration mTmpConfig = new Configuration();
+ // These are protected by mAccessLock.
- // These are protected by the mTmpValue lock.
- private final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
+ /*package*/ final Object mAccessLock = new Object();
+ /*package*/ final Configuration mTmpConfig = new Configuration();
+ /*package*/ TypedValue mTmpValue = new TypedValue();
+ /*package*/ final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
= new LongSparseArray<WeakReference<Drawable.ConstantState> >();
- private final LongSparseArray<WeakReference<ColorStateList> > mColorStateListCache
+ /*package*/ final LongSparseArray<WeakReference<ColorStateList> > mColorStateListCache
= new LongSparseArray<WeakReference<ColorStateList> >();
- private final LongSparseArray<WeakReference<Drawable.ConstantState> > mColorDrawableCache
+ /*package*/ final LongSparseArray<WeakReference<Drawable.ConstantState> > mColorDrawableCache
= new LongSparseArray<WeakReference<Drawable.ConstantState> >();
- private boolean mPreloading;
+ /*package*/ boolean mPreloading;
/*package*/ TypedArray mCachedStyledAttributes = null;
RuntimeException mLastRetrievedAttrs = null;
@@ -118,6 +121,12 @@ public class Resources {
private CompatibilityInfo mCompatibilityInfo;
+ static {
+ sPreloadedDrawables = new LongSparseArray[2];
+ sPreloadedDrawables[0] = new LongSparseArray<Drawable.ConstantState>();
+ sPreloadedDrawables[1] = new LongSparseArray<Drawable.ConstantState>();
+ }
+
/** @hide */
public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
return selectSystemTheme(curTheme, targetSdkVersion,
@@ -196,7 +205,7 @@ public class Resources {
* on orientation, etc).
*/
public static Resources getSystem() {
- synchronized (mSync) {
+ synchronized (sSync) {
Resources ret = mSystem;
if (ret == null) {
ret = new Resources();
@@ -268,7 +277,7 @@ public class Resources {
}
private NativePluralRules getPluralRule() {
- synchronized (mSync) {
+ synchronized (sSync) {
if (mPluralRule == null) {
mPluralRule = NativePluralRules.forLocale(mConfiguration.locale);
}
@@ -524,8 +533,11 @@ public class Resources {
* @see #getDimensionPixelSize
*/
public float getDimension(int id) throws NotFoundException {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(value.data, mMetrics);
@@ -556,8 +568,11 @@ public class Resources {
* @see #getDimensionPixelSize
*/
public int getDimensionPixelOffset(int id) throws NotFoundException {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelOffset(
@@ -590,8 +605,11 @@ public class Resources {
* @see #getDimensionPixelOffset
*/
public int getDimensionPixelSize(int id) throws NotFoundException {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
@@ -621,8 +639,11 @@ public class Resources {
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*/
public float getFraction(int id, int base, int pbase) {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type == TypedValue.TYPE_FRACTION) {
return TypedValue.complexToFraction(value.data, base, pbase);
@@ -661,11 +682,23 @@ public class Resources {
* @return Drawable An object that can be used to draw this resource.
*/
public Drawable getDrawable(int id) throws NotFoundException {
- synchronized (mTmpValue) {
- TypedValue value = mTmpValue;
+ TypedValue value;
+ synchronized (mAccessLock) {
+ value = mTmpValue;
+ if (value == null) {
+ value = new TypedValue();
+ } else {
+ mTmpValue = null;
+ }
getValue(id, value, true);
- return loadDrawable(value, id);
}
+ Drawable res = loadDrawable(value, id);
+ synchronized (mAccessLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
+ }
+ }
+ return res;
}
/**
@@ -688,8 +721,14 @@ public class Resources {
* @return Drawable An object that can be used to draw this resource.
*/
public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
- synchronized (mTmpValue) {
- TypedValue value = mTmpValue;
+ TypedValue value;
+ synchronized (mAccessLock) {
+ value = mTmpValue;
+ if (value == null) {
+ value = new TypedValue();
+ } else {
+ mTmpValue = null;
+ }
getValueForDensity(id, density, value, true);
/*
@@ -706,9 +745,15 @@ public class Resources {
value.density = (value.density * mMetrics.densityDpi) / density;
}
}
+ }
- return loadDrawable(value, id);
+ Drawable res = loadDrawable(value, id);
+ synchronized (mAccessLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
+ }
}
+ return res;
}
/**
@@ -746,20 +791,31 @@ public class Resources {
* @return Returns a single color value in the form 0xAARRGGBB.
*/
public int getColor(int id) throws NotFoundException {
- synchronized (mTmpValue) {
- TypedValue value = mTmpValue;
+ TypedValue value;
+ synchronized (mAccessLock) {
+ value = mTmpValue;
+ if (value == null) {
+ value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
+ mTmpValue = value;
return value.data;
- } else if (value.type == TypedValue.TYPE_STRING) {
- ColorStateList csl = loadColorStateList(mTmpValue, id);
- return csl.getDefaultColor();
+ } else if (value.type != TypedValue.TYPE_STRING) {
+ throw new NotFoundException(
+ "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ + Integer.toHexString(value.type) + " is not valid");
+ }
+ mTmpValue = null;
+ }
+ ColorStateList csl = loadColorStateList(value, id);
+ synchronized (mAccessLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
}
+ return csl.getDefaultColor();
}
/**
@@ -777,11 +833,23 @@ public class Resources {
* solid color or multiple colors that can be selected based on a state.
*/
public ColorStateList getColorStateList(int id) throws NotFoundException {
- synchronized (mTmpValue) {
- TypedValue value = mTmpValue;
+ TypedValue value;
+ synchronized (mAccessLock) {
+ value = mTmpValue;
+ if (value == null) {
+ value = new TypedValue();
+ } else {
+ mTmpValue = null;
+ }
getValue(id, value, true);
- return loadColorStateList(value, id);
}
+ ColorStateList res = loadColorStateList(value, id);
+ synchronized (mAccessLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
+ }
+ }
+ return res;
}
/**
@@ -798,8 +866,11 @@ public class Resources {
* @return Returns the boolean value contained in the resource.
*/
public boolean getBoolean(int id) throws NotFoundException {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
@@ -823,8 +894,11 @@ public class Resources {
* @return Returns the integer value contained in the resource.
*/
public int getInteger(int id) throws NotFoundException {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
@@ -924,9 +998,22 @@ public class Resources {
*
*/
public InputStream openRawResource(int id) throws NotFoundException {
- synchronized (mTmpValue) {
- return openRawResource(id, mTmpValue);
+ TypedValue value;
+ synchronized (mAccessLock) {
+ value = mTmpValue;
+ if (value == null) {
+ value = new TypedValue();
+ } else {
+ mTmpValue = null;
+ }
}
+ InputStream res = openRawResource(id, value);
+ synchronized (mAccessLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
+ }
+ }
+ return res;
}
/**
@@ -978,22 +1065,32 @@ public class Resources {
*
*/
public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
- synchronized (mTmpValue) {
- TypedValue value = mTmpValue;
+ TypedValue value;
+ synchronized (mAccessLock) {
+ value = mTmpValue;
+ if (value == null) {
+ value = new TypedValue();
+ } else {
+ mTmpValue = null;
+ }
getValue(id, value, true);
-
- try {
- return mAssets.openNonAssetFd(
- value.assetCookie, value.string.toString());
- } catch (Exception e) {
- NotFoundException rnf = new NotFoundException(
- "File " + value.string.toString()
- + " from drawable resource ID #0x"
- + Integer.toHexString(id));
- rnf.initCause(e);
- throw rnf;
+ }
+ try {
+ return mAssets.openNonAssetFd(
+ value.assetCookie, value.string.toString());
+ } catch (Exception e) {
+ NotFoundException rnf = new NotFoundException(
+ "File " + value.string.toString()
+ + " from drawable resource ID #0x"
+ + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ } finally {
+ synchronized (mAccessLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
+ }
}
-
}
}
@@ -1125,7 +1222,7 @@ public class Resources {
}
/**
- * Return a StyledAttributes holding the values defined by
+ * Return a TypedArray holding the values defined by
* <var>Theme</var> which are listed in <var>attrs</var>.
*
* <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done
@@ -1153,7 +1250,7 @@ public class Resources {
}
/**
- * Return a StyledAttributes holding the values defined by the style
+ * Return a TypedArray holding the values defined by the style
* resource <var>resid</var> which are listed in <var>attrs</var>.
*
* <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done
@@ -1210,7 +1307,7 @@ public class Resources {
}
/**
- * Return a StyledAttributes holding the attribute values in
+ * Return a TypedArray holding the attribute values in
* <var>set</var>
* that are listed in <var>attrs</var>. In addition, if the given
* AttributeSet specifies a style class (through the "style" attribute),
@@ -1242,10 +1339,10 @@ public class Resources {
* @param attrs The desired attributes to be retrieved.
* @param defStyleAttr An attribute in the current theme that contains a
* reference to a style resource that supplies
- * defaults values for the StyledAttributes. Can be
+ * defaults values for the TypedArray. Can be
* 0 to not look for defaults.
* @param defStyleRes A resource identifier of a style resource that
- * supplies default values for the StyledAttributes,
+ * supplies default values for the TypedArray,
* used only if defStyleAttr is 0 or can not be found
* in the theme. Can be 0 to not look for defaults.
*
@@ -1419,7 +1516,7 @@ public class Resources {
*/
public void updateConfiguration(Configuration config,
DisplayMetrics metrics, CompatibilityInfo compat) {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
if (false) {
Slog.i(TAG, "**** Updating config of " + this + ": old config is "
+ mConfiguration + " old compat is " + mCompatibilityInfo);
@@ -1509,21 +1606,21 @@ public class Resources {
+ " final compat is " + mCompatibilityInfo);
}
- clearDrawableCache(mDrawableCache, configChanges);
- clearDrawableCache(mColorDrawableCache, configChanges);
+ clearDrawableCacheLocked(mDrawableCache, configChanges);
+ clearDrawableCacheLocked(mColorDrawableCache, configChanges);
mColorStateListCache.clear();
flushLayoutCache();
}
- synchronized (mSync) {
+ synchronized (sSync) {
if (mPluralRule != null) {
mPluralRule = NativePluralRules.forLocale(config.locale);
}
}
}
- private void clearDrawableCache(
+ private void clearDrawableCacheLocked(
LongSparseArray<WeakReference<ConstantState>> cache,
int configChanges) {
int N = cache.size();
@@ -1643,6 +1740,9 @@ public class Resources {
* resource was found. (0 is not a valid resource ID.)
*/
public int getIdentifier(String name, String defType, String defPackage) {
+ if (name == null) {
+ throw new NullPointerException("name is null");
+ }
try {
return Integer.parseInt(name);
} catch (Exception e) {
@@ -1652,6 +1752,15 @@ public class Resources {
}
/**
+ * Return true if given resource identifier includes a package.
+ *
+ * @hide
+ */
+ public static boolean resourceHasPackage(int resid) {
+ return (resid >>> 24) != 0;
+ }
+
+ /**
* Return the full name for a given resource identifier. This name is
* a single string of the form "package:type/entry".
*
@@ -1858,7 +1967,7 @@ public class Resources {
* {@hide}
*/
public final void startPreloading() {
- synchronized (mSync) {
+ synchronized (sSync) {
if (sPreloaded) {
throw new IllegalStateException("Resources already preloaded");
}
@@ -1881,23 +1990,42 @@ public class Resources {
}
}
- private boolean verifyPreloadConfig(TypedValue value, String name) {
- if ((value.changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE
- | ActivityInfo.CONFIG_DENSITY)) != 0) {
+ private boolean verifyPreloadConfig(int changingConfigurations, int allowVarying,
+ int resourceId, String name) {
+ // We allow preloading of resources even if they vary by font scale (which
+ // doesn't impact resource selection) or density (which we handle specially by
+ // simply turning off all preloading), as well as any other configs specified
+ // by the caller.
+ if (((changingConfigurations&~(ActivityInfo.CONFIG_FONT_SCALE |
+ ActivityInfo.CONFIG_DENSITY)) & ~allowVarying) != 0) {
String resName;
try {
- resName = getResourceName(value.resourceId);
+ resName = getResourceName(resourceId);
} catch (NotFoundException e) {
resName = "?";
}
Log.w(TAG, "Preloaded " + name + " resource #0x"
- + Integer.toHexString(value.resourceId)
+ + Integer.toHexString(resourceId)
+ " (" + resName + ") that varies with configuration!!");
return false;
}
+ if (TRACE_FOR_PRELOAD) {
+ String resName;
+ try {
+ resName = getResourceName(resourceId);
+ } catch (NotFoundException e) {
+ resName = "?";
+ }
+ Log.w(TAG, "Preloading " + name + " resource #0x"
+ + Integer.toHexString(resourceId)
+ + " (" + resName + ")");
+ }
return true;
}
+ static private final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigToNative(
+ ActivityInfo.CONFIG_LAYOUT_DIRECTION);
+
/*package*/ Drawable loadDrawable(TypedValue value, int id)
throws NotFoundException {
@@ -1922,11 +2050,12 @@ public class Resources {
if (dr != null) {
return dr;
}
-
- Drawable.ConstantState cs = isColorDrawable
- ? sPreloadedColorDrawables.get(key)
- : (sPreloadedDensity == mConfiguration.densityDpi
- ? sPreloadedDrawables.get(key) : null);
+ Drawable.ConstantState cs;
+ if (isColorDrawable) {
+ cs = sPreloadedColorDrawables.get(key);
+ } else {
+ cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
+ }
if (cs != null) {
dr = cs.newDrawable(this);
} else {
@@ -1956,20 +2085,24 @@ public class Resources {
+ value.assetCookie + ": " + file);
if (file.endsWith(".xml")) {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
try {
XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXml(this, rp);
rp.close();
} catch (Exception e) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
NotFoundException rnf = new NotFoundException(
"File " + file + " from drawable resource ID #0x"
+ Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
} else {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
try {
InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
@@ -1979,12 +2112,14 @@ public class Resources {
is.close();
// System.out.println("Created stream: " + dr);
} catch (Exception e) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
NotFoundException rnf = new NotFoundException(
"File " + file + " from drawable resource ID #0x"
+ Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
}
@@ -1994,15 +2129,30 @@ public class Resources {
cs = dr.getConstantState();
if (cs != null) {
if (mPreloading) {
- if (verifyPreloadConfig(value, "drawable")) {
- if (isColorDrawable) {
+ final int changingConfigs = cs.getChangingConfigurations();
+ if (isColorDrawable) {
+ if (verifyPreloadConfig(changingConfigs, 0, value.resourceId,
+ "drawable")) {
sPreloadedColorDrawables.put(key, cs);
- } else {
- sPreloadedDrawables.put(key, cs);
+ }
+ } else {
+ if (verifyPreloadConfig(changingConfigs,
+ LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
+ if ((changingConfigs&LAYOUT_DIR_CONFIG) == 0) {
+ // If this resource does not vary based on layout direction,
+ // we can put it in all of the preload maps.
+ sPreloadedDrawables[0].put(key, cs);
+ sPreloadedDrawables[1].put(key, cs);
+ } else {
+ // Otherwise, only in the layout dir we loaded it for.
+ final LongSparseArray<Drawable.ConstantState> preloads
+ = sPreloadedDrawables[mConfiguration.getLayoutDirection()];
+ preloads.put(key, cs);
+ }
}
}
} else {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
//Log.i(TAG, "Saving cached drawable @ #" +
// Integer.toHexString(key.intValue())
// + " in " + this + ": " + cs);
@@ -2022,7 +2172,7 @@ public class Resources {
private Drawable getCachedDrawable(
LongSparseArray<WeakReference<ConstantState>> drawableCache,
long key) {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
WeakReference<Drawable.ConstantState> wr = drawableCache.get(key);
if (wr != null) { // we have the key
Drawable.ConstantState entry = wr.get();
@@ -2064,7 +2214,8 @@ public class Resources {
csl = ColorStateList.valueOf(value.data);
if (mPreloading) {
- if (verifyPreloadConfig(value, "color")) {
+ if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
+ "color")) {
sPreloadedColorStateLists.put(key, csl);
}
}
@@ -2090,18 +2241,21 @@ public class Resources {
String file = value.string.toString();
if (file.endsWith(".xml")) {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
try {
XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "colorstatelist");
csl = ColorStateList.createFromXml(this, rp);
rp.close();
} catch (Exception e) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
NotFoundException rnf = new NotFoundException(
"File " + file + " from color state list resource ID #0x"
+ Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
} else {
throw new NotFoundException(
"File " + file + " from drawable resource ID #0x"
@@ -2110,11 +2264,12 @@ public class Resources {
if (csl != null) {
if (mPreloading) {
- if (verifyPreloadConfig(value, "color")) {
+ if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
+ "color")) {
sPreloadedColorStateLists.put(key, csl);
}
} else {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
//Log.i(TAG, "Saving cached color state list @ #" +
// Integer.toHexString(key.intValue())
// + " in " + this + ": " + csl);
@@ -2127,7 +2282,7 @@ public class Resources {
}
private ColorStateList getCachedColorStateList(long key) {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
WeakReference<ColorStateList> wr = mColorStateListCache.get(key);
if (wr != null) { // we have the key
ColorStateList entry = wr.get();
@@ -2146,8 +2301,11 @@ public class Resources {
/*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
throws NotFoundException {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
@@ -2209,7 +2367,7 @@ public class Resources {
}
private TypedArray getCachedStyledAttributes(int len) {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedArray attrs = mCachedStyledAttributes;
if (attrs != null) {
mCachedStyledAttributes = null;
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 63e33ce..78180b1 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -16,6 +16,7 @@
package android.content.res;
+import android.graphics.Color;
import android.text.*;
import android.text.style.*;
import android.util.Log;
@@ -24,7 +25,7 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
-import com.android.internal.util.XmlUtils;
+import java.util.Arrays;
/**
* Conveniences for retrieving data out of a compiled string resource.
@@ -33,7 +34,7 @@ import com.android.internal.util.XmlUtils;
*/
final class StringBlock {
private static final String TAG = "AssetManager";
- private static final boolean localLOGV = false || false;
+ private static final boolean localLOGV = false;
private final int mNative;
private final boolean mUseSparse;
@@ -82,7 +83,7 @@ final class StringBlock {
CharSequence res = str;
int[] style = nativeGetStyle(mNative, idx);
if (localLOGV) Log.v(TAG, "Got string: " + str);
- if (localLOGV) Log.v(TAG, "Got styles: " + style);
+ if (localLOGV) Log.v(TAG, "Got styles: " + Arrays.toString(style));
if (style != null) {
if (mStyleIDs == null) {
mStyleIDs = new StyleIDs();
@@ -139,8 +140,12 @@ final class StringBlock {
}
protected void finalize() throws Throwable {
- if (mOwnsNative) {
- nativeDestroy(mNative);
+ try {
+ super.finalize();
+ } finally {
+ if (mOwnsNative) {
+ nativeDestroy(mNative);
+ }
}
}
@@ -236,19 +241,31 @@ final class StringBlock {
sub = subtag(tag, ";fgcolor=");
if (sub != null) {
- int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
- buffer.setSpan(new ForegroundColorSpan(color),
+ buffer.setSpan(getColor(sub, true),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
+ sub = subtag(tag, ";color=");
+ if (sub != null) {
+ buffer.setSpan(getColor(sub, true),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
sub = subtag(tag, ";bgcolor=");
if (sub != null) {
- int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
- buffer.setSpan(new BackgroundColorSpan(color),
+ buffer.setSpan(getColor(sub, false),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
+
+ sub = subtag(tag, ";face=");
+ if (sub != null) {
+ buffer.setSpan(new TypefaceSpan(sub),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
} else if (tag.startsWith("a;")) {
String sub;
@@ -289,6 +306,48 @@ final class StringBlock {
}
/**
+ * Returns a span for the specified color string representation.
+ * If the specified string does not represent a color (null, empty, etc.)
+ * the color black is returned instead.
+ *
+ * @param color The color as a string. Can be a resource reference,
+ * HTML hexadecimal, octal or a name
+ * @param foreground True if the color will be used as the foreground color,
+ * false otherwise
+ *
+ * @return A CharacterStyle
+ *
+ * @see Color#getHtmlColor(String)
+ */
+ private static CharacterStyle getColor(String color, boolean foreground) {
+ int c = 0xff000000;
+
+ if (!TextUtils.isEmpty(color)) {
+ if (color.startsWith("@")) {
+ Resources res = Resources.getSystem();
+ String name = color.substring(1);
+ int colorRes = res.getIdentifier(name, "color", "android");
+ if (colorRes != 0) {
+ ColorStateList colors = res.getColorStateList(colorRes);
+ if (foreground) {
+ return new TextAppearanceSpan(null, 0, 0, colors, null);
+ } else {
+ c = colors.getDefaultColor();
+ }
+ }
+ } else {
+ c = Color.getHtmlColor(color);
+ }
+ }
+
+ if (foreground) {
+ return new ForegroundColorSpan(c);
+ } else {
+ return new BackgroundColorSpan(c);
+ }
+ }
+
+ /**
* If a translator has messed up the edges of paragraph-level markup,
* fix it to actually cover the entire paragraph that it is attached to
* instead of just whatever range they put it on.
@@ -423,11 +482,11 @@ final class StringBlock {
+ ": " + nativeGetSize(mNative));
}
- private static final native int nativeCreate(byte[] data,
+ private static native int nativeCreate(byte[] data,
int offset,
int size);
- private static final native int nativeGetSize(int obj);
- private static final native String nativeGetString(int obj, int idx);
- private static final native int[] nativeGetStyle(int obj, int idx);
- private static final native void nativeDestroy(int obj);
+ private static native int nativeGetSize(int obj);
+ private static native String nativeGetString(int obj, int idx);
+ private static native int[] nativeGetStyle(int obj, int idx);
+ private static native void nativeDestroy(int obj);
}
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 2968fbb..27dddd4 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -687,7 +687,7 @@ public class TypedArray {
* Give back a previously retrieved array, for later re-use.
*/
public void recycle() {
- synchronized (mResources.mTmpValue) {
+ synchronized (mResources.mAccessLock) {
TypedArray cached = mResources.mCachedStyledAttributes;
if (cached == null || cached.mData.length < mData.length) {
mXml = null;
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index 525be96..82a61d4 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -132,6 +132,11 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
}
}
+ /**
+ * Returns an object that contains sufficient metadata to reconstruct
+ * the cursor remotely. May throw if an error occurs when executing the query
+ * and obtaining the row count.
+ */
public BulkCursorDescriptor getBulkCursorDescriptor() {
synchronized (mLock) {
throwIfCursorIsClosed();
diff --git a/core/java/android/util/PoolableManager.java b/core/java/android/database/CursorWindow.aidl
index 8773e63..5809355 100644
--- a/core/java/android/util/PoolableManager.java
+++ b/core/java/android/database/CursorWindow.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-package android.util;
+package android.database;
-/**
- * @hide
- */
-public interface PoolableManager<T extends Poolable<T>> {
- T newInstance();
-
- void onAcquired(T element);
- void onReleased(T element);
-}
+parcelable CursorWindow;
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 0017c46..4f59e8e 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -1077,7 +1077,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
printer.println(" isPrimaryConnection: " + mIsPrimaryConnection);
printer.println(" onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
- mRecentOperations.dump(printer);
+ mRecentOperations.dump(printer, verbose);
if (verbose) {
mPreparedStatementCache.dump(printer);
@@ -1376,7 +1376,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
private void logOperationLocked(int cookie, String detail) {
final Operation operation = getOperationLocked(cookie);
StringBuilder msg = new StringBuilder();
- operation.describe(msg);
+ operation.describe(msg, false);
if (detail != null) {
msg.append(", ").append(detail);
}
@@ -1399,14 +1399,14 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
final Operation operation = mOperations[mIndex];
if (operation != null && !operation.mFinished) {
StringBuilder msg = new StringBuilder();
- operation.describe(msg);
+ operation.describe(msg, false);
return msg.toString();
}
return null;
}
}
- public void dump(Printer printer) {
+ public void dump(Printer printer, boolean verbose) {
synchronized (mOperations) {
printer.println(" Most recently executed operations:");
int index = mIndex;
@@ -1418,7 +1418,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
msg.append(" ").append(n).append(": [");
msg.append(operation.getFormattedStartTime());
msg.append("] ");
- operation.describe(msg);
+ operation.describe(msg, verbose);
printer.println(msg.toString());
if (index > 0) {
@@ -1449,7 +1449,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
public Exception mException;
public int mCookie;
- public void describe(StringBuilder msg) {
+ public void describe(StringBuilder msg, boolean verbose) {
msg.append(mKind);
if (mFinished) {
msg.append(" took ").append(mEndTime - mStartTime).append("ms");
@@ -1461,7 +1461,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
if (mSql != null) {
msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\"");
}
- if (mBindArgs != null && mBindArgs.size() != 0) {
+ if (verbose && mBindArgs != null && mBindArgs.size() != 0) {
msg.append(", bindArgs=[");
final int count = mBindArgs.size();
for (int i = 0; i < count; i++) {
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index b29897e..5a1a8e2 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -138,17 +138,26 @@ public class SQLiteCursor extends AbstractWindowedCursor {
private void fillWindow(int requiredPos) {
clearOrCreateWindow(getDatabase().getPath());
- if (mCount == NO_COUNT) {
- int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
- mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
- mCursorWindowCapacity = mWindow.getNumRows();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
+ try {
+ if (mCount == NO_COUNT) {
+ int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
+ mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
+ mCursorWindowCapacity = mWindow.getNumRows();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
+ }
+ } else {
+ int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
+ mCursorWindowCapacity);
+ mQuery.fillWindow(mWindow, startPos, requiredPos, false);
}
- } else {
- int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
- mCursorWindowCapacity);
- mQuery.fillWindow(mWindow, startPos, requiredPos, false);
+ } catch (RuntimeException ex) {
+ // Close the cursor window if the query failed and therefore will
+ // not produce any results. This helps to avoid accidentally leaking
+ // the cursor window if the client does not correctly handle exceptions
+ // and fails to close the cursor.
+ closeWindow();
+ throw ex;
}
}
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index e99fa92..842a482 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -36,6 +36,10 @@ public class DdmHandleHello extends ChunkHandler {
private static DdmHandleHello mInstance = new DdmHandleHello();
+ private static final String[] FRAMEWORK_FEATURES = new String[] {
+ "opengl-tracing",
+ "view-hierarchy",
+ };
/* singleton, do not instantiate */
private DdmHandleHello() {}
@@ -149,21 +153,27 @@ public class DdmHandleHello extends ChunkHandler {
private Chunk handleFEAT(Chunk request) {
// TODO: query the VM to ensure that support for these features
// is actually compiled in
- final String[] features = Debug.getVmFeatureList();
+ final String[] vmFeatures = Debug.getVmFeatureList();
if (false)
Log.v("ddm-heap", "Got feature list request");
- int size = 4 + 4 * features.length;
- for (int i = features.length-1; i >= 0; i--)
- size += features[i].length() * 2;
+ int size = 4 + 4 * (vmFeatures.length + FRAMEWORK_FEATURES.length);
+ for (int i = vmFeatures.length-1; i >= 0; i--)
+ size += vmFeatures[i].length() * 2;
+ for (int i = FRAMEWORK_FEATURES.length-1; i>= 0; i--)
+ size += FRAMEWORK_FEATURES[i].length() * 2;
ByteBuffer out = ByteBuffer.allocate(size);
out.order(ChunkHandler.CHUNK_ORDER);
- out.putInt(features.length);
- for (int i = features.length-1; i >= 0; i--) {
- out.putInt(features[i].length());
- putString(out, features[i]);
+ out.putInt(vmFeatures.length + FRAMEWORK_FEATURES.length);
+ for (int i = vmFeatures.length-1; i >= 0; i--) {
+ out.putInt(vmFeatures[i].length());
+ putString(out, vmFeatures[i]);
+ }
+ for (int i = FRAMEWORK_FEATURES.length-1; i >= 0; i--) {
+ out.putInt(FRAMEWORK_FEATURES[i].length());
+ putString(out, FRAMEWORK_FEATURES[i]);
}
return new Chunk(CHUNK_FEAT, out);
diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java
new file mode 100644
index 0000000..ce83796
--- /dev/null
+++ b/core/java/android/ddm/DdmHandleViewDebug.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.ddm;
+
+import android.opengl.GLUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewRootImpl;
+import android.view.WindowManagerGlobal;
+
+import org.apache.harmony.dalvik.ddmc.Chunk;
+import org.apache.harmony.dalvik.ddmc.ChunkHandler;
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.Method;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Handle various requests related to profiling / debugging of the view system.
+ * Support for these features are advertised via {@link DdmHandleHello}.
+ */
+public class DdmHandleViewDebug extends ChunkHandler {
+ /** Enable/Disable tracing of OpenGL calls. */
+ public static final int CHUNK_VUGL = type("VUGL");
+
+ /** List {@link ViewRootImpl}'s of this process. */
+ private static final int CHUNK_VULW = type("VULW");
+
+ /** Operation on view root, first parameter in packet should be one of VURT_* constants */
+ private static final int CHUNK_VURT = type("VURT");
+
+ /** Dump view hierarchy. */
+ private static final int VURT_DUMP_HIERARCHY = 1;
+
+ /** Capture View Layers. */
+ private static final int VURT_CAPTURE_LAYERS = 2;
+
+ /**
+ * Generic View Operation, first parameter in the packet should be one of the
+ * VUOP_* constants below.
+ */
+ private static final int CHUNK_VUOP = type("VUOP");
+
+ /** Capture View. */
+ private static final int VUOP_CAPTURE_VIEW = 1;
+
+ /** Obtain the Display List corresponding to the view. */
+ private static final int VUOP_DUMP_DISPLAYLIST = 2;
+
+ /** Profile a view. */
+ private static final int VUOP_PROFILE_VIEW = 3;
+
+ /** Invoke a method on the view. */
+ private static final int VUOP_INVOKE_VIEW_METHOD = 4;
+
+ /** Set layout parameter. */
+ private static final int VUOP_SET_LAYOUT_PARAMETER = 5;
+
+ /** Error code indicating operation specified in chunk is invalid. */
+ private static final int ERR_INVALID_OP = -1;
+
+ /** Error code indicating that the parameters are invalid. */
+ private static final int ERR_INVALID_PARAM = -2;
+
+ /** Error code indicating an exception while performing operation. */
+ private static final int ERR_EXCEPTION = -3;
+
+ private static final String TAG = "DdmViewDebug";
+
+ private static final DdmHandleViewDebug sInstance = new DdmHandleViewDebug();
+
+ /** singleton, do not instantiate. */
+ private DdmHandleViewDebug() {}
+
+ public static void register() {
+ DdmServer.registerHandler(CHUNK_VUGL, sInstance);
+ DdmServer.registerHandler(CHUNK_VULW, sInstance);
+ DdmServer.registerHandler(CHUNK_VURT, sInstance);
+ DdmServer.registerHandler(CHUNK_VUOP, sInstance);
+ }
+
+ @Override
+ public void connected() {
+ }
+
+ @Override
+ public void disconnected() {
+ }
+
+ @Override
+ public Chunk handleChunk(Chunk request) {
+ int type = request.type;
+
+ if (type == CHUNK_VUGL) {
+ return handleOpenGlTrace(request);
+ } else if (type == CHUNK_VULW) {
+ return listWindows();
+ }
+
+ ByteBuffer in = wrapChunk(request);
+ int op = in.getInt();
+
+ View rootView = getRootView(in);
+ if (rootView == null) {
+ return createFailChunk(ERR_INVALID_PARAM, "Invalid View Root");
+ }
+
+ if (type == CHUNK_VURT) {
+ if (op == VURT_DUMP_HIERARCHY)
+ return dumpHierarchy(rootView, in);
+ else if (op == VURT_CAPTURE_LAYERS)
+ return captureLayers(rootView);
+ else
+ return createFailChunk(ERR_INVALID_OP, "Unknown view root operation: " + op);
+ }
+
+ final View targetView = getTargetView(rootView, in);
+ if (targetView == null) {
+ return createFailChunk(ERR_INVALID_PARAM, "Invalid target view");
+ }
+
+ if (type == CHUNK_VUOP) {
+ switch (op) {
+ case VUOP_CAPTURE_VIEW:
+ return captureView(rootView, targetView);
+ case VUOP_DUMP_DISPLAYLIST:
+ return dumpDisplayLists(rootView, targetView);
+ case VUOP_PROFILE_VIEW:
+ return profileView(rootView, targetView);
+ case VUOP_INVOKE_VIEW_METHOD:
+ return invokeViewMethod(rootView, targetView, in);
+ case VUOP_SET_LAYOUT_PARAMETER:
+ return setLayoutParameter(rootView, targetView, in);
+ default:
+ return createFailChunk(ERR_INVALID_OP, "Unknown view operation: " + op);
+ }
+ } else {
+ throw new RuntimeException("Unknown packet " + ChunkHandler.name(type));
+ }
+ }
+
+ private Chunk handleOpenGlTrace(Chunk request) {
+ ByteBuffer in = wrapChunk(request);
+ GLUtils.setTracingLevel(in.getInt());
+ return null; // empty response
+ }
+
+ /** Returns the list of windows owned by this client. */
+ private Chunk listWindows() {
+ String[] windowNames = WindowManagerGlobal.getInstance().getViewRootNames();
+
+ int responseLength = 4; // # of windows
+ for (String name : windowNames) {
+ responseLength += 4; // length of next window name
+ responseLength += name.length() * 2; // window name
+ }
+
+ ByteBuffer out = ByteBuffer.allocate(responseLength);
+ out.order(ChunkHandler.CHUNK_ORDER);
+
+ out.putInt(windowNames.length);
+ for (String name : windowNames) {
+ out.putInt(name.length());
+ putString(out, name);
+ }
+
+ return new Chunk(CHUNK_VULW, out);
+ }
+
+ private View getRootView(ByteBuffer in) {
+ try {
+ int viewRootNameLength = in.getInt();
+ String viewRootName = getString(in, viewRootNameLength);
+ return WindowManagerGlobal.getInstance().getRootView(viewRootName);
+ } catch (BufferUnderflowException e) {
+ return null;
+ }
+ }
+
+ private View getTargetView(View root, ByteBuffer in) {
+ int viewLength;
+ String viewName;
+
+ try {
+ viewLength = in.getInt();
+ viewName = getString(in, viewLength);
+ } catch (BufferUnderflowException e) {
+ return null;
+ }
+
+ return ViewDebug.findView(root, viewName);
+ }
+
+ /**
+ * Returns the view hierarchy and/or view properties starting at the provided view.
+ * Based on the input options, the return data may include:
+ * - just the view hierarchy
+ * - view hierarchy & the properties for each of the views
+ * - just the view properties for a specific view.
+ * TODO: Currently this only returns views starting at the root, need to fix so that
+ * it can return properties of any view.
+ */
+ private Chunk dumpHierarchy(View rootView, ByteBuffer in) {
+ boolean skipChildren = in.getInt() > 0;
+ boolean includeProperties = in.getInt() > 0;
+
+ ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
+ try {
+ ViewDebug.dump(rootView, skipChildren, includeProperties, b);
+ } catch (IOException e) {
+ return createFailChunk(1, "Unexpected error while obtaining view hierarchy: "
+ + e.getMessage());
+ }
+
+ byte[] data = b.toByteArray();
+ return new Chunk(CHUNK_VURT, data, 0, data.length);
+ }
+
+ /** Returns a buffer with region details & bitmap of every single view. */
+ private Chunk captureLayers(View rootView) {
+ ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
+ DataOutputStream dos = new DataOutputStream(b);
+ try {
+ ViewDebug.captureLayers(rootView, dos);
+ } catch (IOException e) {
+ return createFailChunk(1, "Unexpected error while obtaining view hierarchy: "
+ + e.getMessage());
+ } finally {
+ try {
+ dos.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ byte[] data = b.toByteArray();
+ return new Chunk(CHUNK_VURT, data, 0, data.length);
+ }
+
+ private Chunk captureView(View rootView, View targetView) {
+ ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
+ try {
+ ViewDebug.capture(rootView, b, targetView);
+ } catch (IOException e) {
+ return createFailChunk(1, "Unexpected error while capturing view: "
+ + e.getMessage());
+ }
+
+ byte[] data = b.toByteArray();
+ return new Chunk(CHUNK_VUOP, data, 0, data.length);
+ }
+
+ /** Returns the display lists corresponding to the provided view. */
+ private Chunk dumpDisplayLists(final View rootView, final View targetView) {
+ rootView.post(new Runnable() {
+ @Override
+ public void run() {
+ ViewDebug.outputDisplayList(rootView, targetView);
+ }
+ });
+ return null;
+ }
+
+ /**
+ * Invokes provided method on the view.
+ * The method name and its arguments are passed in as inputs via the byte buffer.
+ * The buffer contains:<ol>
+ * <li> len(method name) </li>
+ * <li> method name </li>
+ * <li> # of args </li>
+ * <li> arguments: Each argument comprises of a type specifier followed by the actual argument.
+ * The type specifier is a single character as used in JNI:
+ * (Z - boolean, B - byte, C - char, S - short, I - int, J - long,
+ * F - float, D - double). <p>
+ * The type specifier is followed by the actual value of argument.
+ * Booleans are encoded via bytes with 0 indicating false.</li>
+ * </ol>
+ * Methods that take no arguments need only specify the method name.
+ */
+ private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
+ int l = in.getInt();
+ String methodName = getString(in, l);
+
+ Class<?>[] argTypes;
+ Object[] args;
+ if (!in.hasRemaining()) {
+ argTypes = new Class<?>[0];
+ args = new Object[0];
+ } else {
+ int nArgs = in.getInt();
+
+ argTypes = new Class<?>[nArgs];
+ args = new Object[nArgs];
+
+ for (int i = 0; i < nArgs; i++) {
+ char c = in.getChar();
+ switch (c) {
+ case 'Z':
+ argTypes[i] = boolean.class;
+ args[i] = in.get() == 0 ? false : true;
+ break;
+ case 'B':
+ argTypes[i] = byte.class;
+ args[i] = in.get();
+ break;
+ case 'C':
+ argTypes[i] = char.class;
+ args[i] = in.getChar();
+ break;
+ case 'S':
+ argTypes[i] = short.class;
+ args[i] = in.getShort();
+ break;
+ case 'I':
+ argTypes[i] = int.class;
+ args[i] = in.getInt();
+ break;
+ case 'J':
+ argTypes[i] = long.class;
+ args[i] = in.getLong();
+ break;
+ case 'F':
+ argTypes[i] = float.class;
+ args[i] = in.getFloat();
+ break;
+ case 'D':
+ argTypes[i] = double.class;
+ args[i] = in.getDouble();
+ break;
+ default:
+ Log.e(TAG, "arg " + i + ", unrecognized type: " + c);
+ return createFailChunk(ERR_INVALID_PARAM,
+ "Unsupported parameter type (" + c + ") to invoke view method.");
+ }
+ }
+ }
+
+ Method method = null;
+ try {
+ method = targetView.getClass().getMethod(methodName, argTypes);
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "No such method: " + e.getMessage());
+ return createFailChunk(ERR_INVALID_PARAM,
+ "No such method: " + e.getMessage());
+ }
+
+ try {
+ ViewDebug.invokeViewMethod(targetView, method, args);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
+ String msg = e.getCause().getMessage();
+ if (msg == null) {
+ msg = e.getCause().toString();
+ }
+ return createFailChunk(ERR_EXCEPTION, msg);
+ }
+
+ return null;
+ }
+
+ private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) {
+ int l = in.getInt();
+ String param = getString(in, l);
+ int value = in.getInt();
+ try {
+ ViewDebug.setLayoutParameter(targetView, param, value);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception setting layout parameter: " + e);
+ return createFailChunk(ERR_EXCEPTION, "Error accessing field "
+ + param + ":" + e.getMessage());
+ }
+
+ return null;
+ }
+
+ /** Profiles provided view. */
+ private Chunk profileView(View rootView, final View targetView) {
+ ByteArrayOutputStream b = new ByteArrayOutputStream(32 * 1024);
+ BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(b), 32 * 1024);
+ try {
+ ViewDebug.profileViewAndChildren(targetView, bw);
+ } catch (IOException e) {
+ return createFailChunk(1, "Unexpected error while profiling view: " + e.getMessage());
+ } finally {
+ try {
+ bw.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ byte[] data = b.toByteArray();
+ return new Chunk(CHUNK_VUOP, data, 0, data.length);
+ }
+}
diff --git a/core/java/android/ddm/DdmRegister.java b/core/java/android/ddm/DdmRegister.java
index ecd450d..e0faa51 100644
--- a/core/java/android/ddm/DdmRegister.java
+++ b/core/java/android/ddm/DdmRegister.java
@@ -51,6 +51,7 @@ public class DdmRegister {
DdmHandleNativeHeap.register();
DdmHandleProfiling.register();
DdmHandleExit.register();
+ DdmHandleViewDebug.register();
DdmServer.registrationComplete();
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 483e9de..4e51080 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -16,6 +16,7 @@
package android.hardware;
+import android.app.ActivityThread;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
@@ -337,7 +338,9 @@ public class Camera {
mEventHandler = null;
}
- native_setup(new WeakReference<Camera>(this), cameraId);
+ String packageName = ActivityThread.currentPackageName();
+
+ native_setup(new WeakReference<Camera>(this), cameraId, packageName);
}
/**
@@ -350,7 +353,9 @@ public class Camera {
release();
}
- private native final void native_setup(Object camera_this, int cameraId);
+ private native final void native_setup(Object camera_this, int cameraId,
+ String packageName);
+
private native final void native_release();
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index e0c9d2c..5cc1150 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -17,6 +17,8 @@
package android.hardware;
+import android.os.Build;
+
/**
* Class representing a sensor. Use {@link SensorManager#getSensorList} to get
* the list of available Sensors.
@@ -29,23 +31,23 @@ package android.hardware;
public final class Sensor {
/**
- * A constant describing an accelerometer sensor type. See
- * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
- * details.
+ * A constant describing an accelerometer sensor type.
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
+ * for more details.
*/
public static final int TYPE_ACCELEROMETER = 1;
/**
- * A constant describing a magnetic field sensor type. See
- * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
- * details.
+ * A constant describing a magnetic field sensor type.
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
+ * for more details.
*/
public static final int TYPE_MAGNETIC_FIELD = 2;
/**
- * A constant describing an orientation sensor type. See
- * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
- * details.
+ * A constant describing an orientation sensor type.
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
+ * for more details.
*
* @deprecated use {@link android.hardware.SensorManager#getOrientation
* SensorManager.getOrientation()} instead.
@@ -53,17 +55,21 @@ public final class Sensor {
@Deprecated
public static final int TYPE_ORIENTATION = 3;
- /** A constant describing a gyroscope sensor type */
+ /** A constant describing a gyroscope sensor type.
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
+ * for more details. */
public static final int TYPE_GYROSCOPE = 4;
/**
- * A constant describing a light sensor type. See
- * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
- * details.
+ * A constant describing a light sensor type.
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
+ * for more details.
*/
public static final int TYPE_LIGHT = 5;
- /** A constant describing a pressure sensor type */
+ /** A constant describing a pressure sensor type.
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
+ * for more details. */
public static final int TYPE_PRESSURE = 6;
/**
@@ -77,48 +83,160 @@ public final class Sensor {
public static final int TYPE_TEMPERATURE = 7;
/**
- * A constant describing a proximity sensor type. See
- * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
- * details.
+ * A constant describing a proximity sensor type.
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
+ * for more details.
*/
public static final int TYPE_PROXIMITY = 8;
/**
* A constant describing a gravity sensor type.
- * See {@link android.hardware.SensorEvent SensorEvent}
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
* for more details.
*/
public static final int TYPE_GRAVITY = 9;
/**
* A constant describing a linear acceleration sensor type.
- * See {@link android.hardware.SensorEvent SensorEvent}
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
* for more details.
*/
public static final int TYPE_LINEAR_ACCELERATION = 10;
/**
* A constant describing a rotation vector sensor type.
- * See {@link android.hardware.SensorEvent SensorEvent}
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
* for more details.
*/
public static final int TYPE_ROTATION_VECTOR = 11;
/**
* A constant describing a relative humidity sensor type.
- * See {@link android.hardware.SensorEvent SensorEvent}
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
* for more details.
*/
public static final int TYPE_RELATIVE_HUMIDITY = 12;
- /** A constant describing an ambient temperature sensor type */
+ /** A constant describing an ambient temperature sensor type.
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
+ * for more details. */
public static final int TYPE_AMBIENT_TEMPERATURE = 13;
- /**
+ /**
+ * A constant describing an uncalibrated magnetic field sensor type.
+ * <p>
+ * Similar to {@link #TYPE_MAGNETIC_FIELD} but the hard iron calibration (device calibration
+ * due to distortions that arise from magnetized iron, steel or permanent magnets on the
+ * device) is not considered in the given sensor values. However, such hard iron bias values
+ * are returned to you separately in the result {@link android.hardware.SensorEvent#values}
+ * so you may use them for custom calibrations.
+ * <p>Also, no periodic calibration is performed
+ * (i.e. there are no discontinuities in the data stream while using this sensor) and
+ * assumptions that the magnetic field is due to the Earth's poles is avoided, but
+ * factory calibration and temperature compensation have been performed.
+ * </p>
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values} for more
+ * details.
+ */
+ public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14;
+
+ /**
+ * A constant describing an uncalibrated rotation vector sensor type.
+ * <p>Identical to {@link #TYPE_ROTATION_VECTOR} except that it doesn't
+ * use the geomagnetic field. Therefore the Y axis doesn't
+ * point north, but instead to some other reference, that reference is
+ * allowed to drift by the same order of magnitude as the gyroscope
+ * drift around the Z axis.
+ * <p>
+ * In the ideal case, a phone rotated and returning to the same real-world
+ * orientation should report the same game rotation vector
+ * (without using the earth's geomagnetic field). However, the orientation
+ * may drift somewhat over time.
+ * </p>
+ * <p>See {@link android.hardware.SensorEvent#values SensorEvent.values} for more
+ * details.
+ */
+
+ public static final int TYPE_GAME_ROTATION_VECTOR = 15;
+
+ /**
+ * A constant describing an uncalibrated gyroscope sensor type.
+ * <p>Similar to {@link #TYPE_GYROSCOPE} but no gyro-drift compensation has been performed
+ * to adjust the given sensor values. However, such gyro-drift bias values
+ * are returned to you separately in the result {@link android.hardware.SensorEvent#values}
+ * so you may use them for custom calibrations.
+ * <p>Factory calibration and temperature compensation is still applied
+ * to the rate of rotation (angular speeds).
+ * </p>
+ * <p> See {@link android.hardware.SensorEvent#values SensorEvent.values} for more
+ * details.
+ */
+ public static final int TYPE_GYROSCOPE_UNCALIBRATED = 16;
+
+ /**
+ * A constant describing the significant motion trigger sensor.
+ * <p>
+ * It triggers when an event occurs and then automatically disables
+ * itself. The sensor continues to operate while the device is asleep
+ * and will automatically wake the device to notify when significant
+ * motion is detected. The application does not need to hold any wake
+ * locks for this sensor to trigger.
+ * <p>See {@link TriggerEvent} for more details.
+ */
+ public static final int TYPE_SIGNIFICANT_MOTION = 17;
+
+ /**
* A constant describing all sensor types.
*/
public static final int TYPE_ALL = -1;
+ /* Reporting mode constants for sensors. Each sensor will have exactly one
+ reporting mode associated with it. */
+ // Events are reported at a constant rate.
+ static int REPORTING_MODE_CONTINUOUS = 1;
+
+ // Events are reported only when the value changes.
+ static int REPORTING_MODE_ON_CHANGE = 2;
+
+ // Upon detection of an event, the sensor deactivates itself and then sends a single event.
+ static int REPORTING_MODE_ONE_SHOT = 3;
+
+ // TODO(): The following arrays are fragile and error-prone. This needs to be refactored.
+
+ // Note: This needs to be updated, whenever a new sensor is added.
+ private static int[] sSensorReportingModes = {
+ REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS,
+ REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS,
+ REPORTING_MODE_ON_CHANGE, REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS,
+ REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ON_CHANGE,
+ REPORTING_MODE_ON_CHANGE, REPORTING_MODE_CONTINUOUS, REPORTING_MODE_CONTINUOUS,
+ REPORTING_MODE_CONTINUOUS, REPORTING_MODE_ONE_SHOT };
+
+ // Note: This needs to be updated, whenever a new sensor is added.
+ // Holds the maximum length of the values array associated with {@link SensorEvent} or
+ // {@link TriggerEvent} for the Sensor
+ private static int[] sMaxLengthValuesArray = {
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 3, 3,
+ 6, 4, 6, 1 };
+
+ static int getReportingMode(Sensor sensor) {
+ // mType starts from offset 1.
+ return sSensorReportingModes[sensor.mType - 1];
+ }
+
+ static int getMaxLengthValuesArray(Sensor sensor, int sdkLevel) {
+ // mType starts from offset 1.
+ int len = sMaxLengthValuesArray[sensor.mType - 1];
+
+ // RotationVector length has changed to 3 to 5 for API level 18
+ // Set it to 3 for backward compatibility.
+ if (sensor.getType() == Sensor.TYPE_ROTATION_VECTOR &&
+ sdkLevel <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ len = 3;
+ }
+ return len;
+ }
+
/* Some of these fields are set only by the native bindings in
* SensorManager.
*/
@@ -132,7 +250,6 @@ public final class Sensor {
private float mPower;
private int mMinDelay;
-
Sensor() {
}
@@ -194,7 +311,8 @@ public final class Sensor {
return mMinDelay;
}
- int getHandle() {
+ /** @hide */
+ public int getHandle() {
return mHandle;
}
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 6166d2c..2bc0f9b 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -17,11 +17,9 @@
package android.hardware;
/**
- * <p>
* This class represents a {@link android.hardware.Sensor Sensor} event and
* holds informations such as the sensor's type, the time-stamp, accuracy and of
* course the sensor's {@link SensorEvent#values data}.
- * </p>
*
* <p>
* <u>Definition of the coordinate system used by the SensorEvent API.</u>
@@ -65,46 +63,40 @@ public class SensorEvent {
*
* <h4>{@link android.hardware.Sensor#TYPE_ACCELEROMETER
* Sensor.TYPE_ACCELEROMETER}:</h4> All values are in SI units (m/s^2)
- *
+ *
* <ul>
- * <p>
- * values[0]: Acceleration minus Gx on the x-axis
- * </p>
- * <p>
- * values[1]: Acceleration minus Gy on the y-axis
- * </p>
- * <p>
- * values[2]: Acceleration minus Gz on the z-axis
- * </p>
+ * <li> values[0]: Acceleration minus Gx on the x-axis </li>
+ * <li> values[1]: Acceleration minus Gy on the y-axis </li>
+ * <li> values[2]: Acceleration minus Gz on the z-axis </li>
* </ul>
- *
+ *
* <p>
* A sensor of this type measures the acceleration applied to the device
* (<b>Ad</b>). Conceptually, it does so by measuring forces applied to the
* sensor itself (<b>Fs</b>) using the relation:
* </p>
- *
+ *
* <b><center>Ad = - &#8721;Fs / mass</center></b>
- *
+ *
* <p>
* In particular, the force of gravity is always influencing the measured
* acceleration:
* </p>
- *
+ *
* <b><center>Ad = -g - &#8721;F / mass</center></b>
- *
+ *
* <p>
* For this reason, when the device is sitting on a table (and obviously not
* accelerating), the accelerometer reads a magnitude of <b>g</b> = 9.81
* m/s^2
* </p>
- *
+ *
* <p>
* Similarly, when the device is in free-fall and therefore dangerously
* accelerating towards to ground at 9.81 m/s^2, its accelerometer reads a
* magnitude of 0 m/s^2.
* </p>
- *
+ *
* <p>
* It should be apparent that in order to measure the real acceleration of
* the device, the contribution of the force of gravity must be eliminated.
@@ -137,23 +129,23 @@ public class SensorEvent {
* <ul>
* <li>When the device lies flat on a table and is pushed on its left side
* toward the right, the x acceleration value is positive.</li>
- *
+ *
* <li>When the device lies flat on a table, the acceleration value is
* +9.81, which correspond to the acceleration of the device (0 m/s^2) minus
* the force of gravity (-9.81 m/s^2).</li>
- *
+ *
* <li>When the device lies flat on a table and is pushed toward the sky
* with an acceleration of A m/s^2, the acceleration value is equal to
* A+9.81 which correspond to the acceleration of the device (+A m/s^2)
* minus the force of gravity (-9.81 m/s^2).</li>
* </ul>
- *
- *
+ *
+ *
* <h4>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD
* Sensor.TYPE_MAGNETIC_FIELD}:</h4>
* All values are in micro-Tesla (uT) and measure the ambient magnetic field
* in the X, Y and Z axis.
- *
+ *
* <h4>{@link android.hardware.Sensor#TYPE_GYROSCOPE Sensor.TYPE_GYROSCOPE}:
* </h4> All values are in radians/second and measure the rate of rotation
* around the device's local X, Y and Z axis. The coordinate system is the
@@ -165,15 +157,9 @@ public class SensorEvent {
* definition of positive rotation and does not agree with the definition of
* roll given earlier.
* <ul>
- * <p>
- * values[0]: Angular speed around the x-axis
- * </p>
- * <p>
- * values[1]: Angular speed around the y-axis
- * </p>
- * <p>
- * values[2]: Angular speed around the z-axis
- * </p>
+ * <li> values[0]: Angular speed around the x-axis </li>
+ * <li> values[1]: Angular speed around the y-axis </li>
+ * <li> values[2]: Angular speed around the z-axis </li>
* </ul>
* <p>
* Typically the output of the gyroscope is integrated over time to
@@ -233,31 +219,28 @@ public class SensorEvent {
* </p>
* <h4>{@link android.hardware.Sensor#TYPE_LIGHT Sensor.TYPE_LIGHT}:</h4>
* <ul>
- * <p>
- * values[0]: Ambient light level in SI lux units
+ * <li>values[0]: Ambient light level in SI lux units </li>
* </ul>
- *
+ *
* <h4>{@link android.hardware.Sensor#TYPE_PRESSURE Sensor.TYPE_PRESSURE}:</h4>
* <ul>
- * <p>
- * values[0]: Atmospheric pressure in hPa (millibar)
+ * <li>values[0]: Atmospheric pressure in hPa (millibar) </li>
* </ul>
*
* <h4>{@link android.hardware.Sensor#TYPE_PROXIMITY Sensor.TYPE_PROXIMITY}:
* </h4>
- *
+ *
* <ul>
- * <p>
- * values[0]: Proximity sensor distance measured in centimeters
+ * <li>values[0]: Proximity sensor distance measured in centimeters </li>
* </ul>
- *
+ *
* <p>
* <b>Note:</b> Some proximity sensors only support a binary <i>near</i> or
* <i>far</i> measurement. In this case, the sensor should report its
* {@link android.hardware.Sensor#getMaximumRange() maximum range} value in
* the <i>far</i> state and a lesser value in the <i>near</i> state.
* </p>
- *
+ *
* <h4>{@link android.hardware.Sensor#TYPE_GRAVITY Sensor.TYPE_GRAVITY}:</h4>
* <p>A three dimensional vector indicating the direction and magnitude of gravity. Units
* are m/s^2. The coordinate system is the same as is used by the acceleration sensor.</p>
@@ -304,47 +287,42 @@ public class SensorEvent {
* </p>
*
* <ul>
- * <p>
- * values[0]: x*sin(&#952/2)
- * </p>
- * <p>
- * values[1]: y*sin(&#952/2)
- * </p>
- * <p>
- * values[2]: z*sin(&#952/2)
- * </p>
- * <p>
- * values[3]: cos(&#952/2) <i>(optional: only if value.length = 4)</i>
- * </p>
+ * <li> values[0]: x*sin(&#952/2) </li>
+ * <li> values[1]: y*sin(&#952/2) </li>
+ * <li> values[2]: z*sin(&#952/2) </li>
+ * <li> values[3]: cos(&#952/2) </li>
+ * <li> values[4]: estimated heading Accuracy (in radians) (-1 if unavailable)</li>
* </ul>
+ * <p> values[3], originally optional, will always be present from SDK Level 18 onwards.
+ * values[4] is a new value that has been added in SDK Level 18.
+ * </p>
*
* <h4>{@link android.hardware.Sensor#TYPE_ORIENTATION
* Sensor.TYPE_ORIENTATION}:</h4> All values are angles in degrees.
- *
+ *
* <ul>
- * <p>
- * values[0]: Azimuth, angle between the magnetic north direction and the
+ * <li> values[0]: Azimuth, angle between the magnetic north direction and the
* y-axis, around the z-axis (0 to 359). 0=North, 90=East, 180=South,
* 270=West
* </p>
- *
+ *
* <p>
* values[1]: Pitch, rotation around x-axis (-180 to 180), with positive
* values when the z-axis moves <b>toward</b> the y-axis.
* </p>
- *
+ *
* <p>
* values[2]: Roll, rotation around the x-axis (-90 to 90)
* increasing as the device moves clockwise.
* </p>
* </ul>
- *
+ *
* <p>
* <b>Note:</b> This definition is different from <b>yaw, pitch and roll</b>
* used in aviation where the X axis is along the long side of the plane
* (tail to nose).
* </p>
- *
+ *
* <p>
* <b>Note:</b> This sensor type exists for legacy reasons, please use
* {@link android.hardware.SensorManager#getRotationMatrix
@@ -354,7 +332,7 @@ public class SensorEvent {
* {@link android.hardware.SensorManager#getOrientation getOrientation()} to
* compute these values instead.
* </p>
- *
+ *
* <p>
* <b>Important note:</b> For historical reasons the roll angle is positive
* in the clockwise direction (mathematically speaking, it should be
@@ -364,9 +342,7 @@ public class SensorEvent {
* <h4>{@link android.hardware.Sensor#TYPE_RELATIVE_HUMIDITY
* Sensor.TYPE_RELATIVE_HUMIDITY}:</h4>
* <ul>
- * <p>
- * values[0]: Relative ambient air humidity in percent
- * </p>
+ * <li> values[0]: Relative ambient air humidity in percent </li>
* </ul>
* <p>
* When relative ambient air humidity and ambient temperature are
@@ -423,21 +399,96 @@ public class SensorEvent {
* </h4>
*
* <ul>
+ * <li> values[0]: ambient (room) temperature in degree Celsius.</li>
+ * </ul>
+ *
+ *
+ * <h4>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD_UNCALIBRATED
+ * Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED}:</h4>
+ * Similar to {@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD},
+ * but the hard iron calibration is reported separately instead of being included
+ * in the measurement. Factory calibration and temperature compensation will still
+ * be applied to the "uncalibrated" measurement. Assumptions that the magnetic field
+ * is due to the Earth's poles is avoided.
+ * <p>
+ * The values array is shown below:
+ * <ul>
+ * <li> values[0] = x_uncalib </li>
+ * <li> values[1] = y_uncalib </li>
+ * <li> values[2] = z_uncalib </li>
+ * <li> values[3] = x_bias </li>
+ * <li> values[4] = y_bias </li>
+ * <li> values[5] = z_bias </li>
+ * </ul>
+ * </p>
+ * <p>
+ * x_uncalib, y_uncalib, z_uncalib are the measured magnetic field in X, Y, Z axes.
+ * Soft iron and temperature calibrations are applied. But the hard iron
+ * calibration is not applied. The values are in micro-Tesla (uT).
+ * </p>
* <p>
- * values[0]: ambient (room) temperature in degree Celsius.
+ * x_bias, y_bias, z_bias give the iron bias estimated in X, Y, Z axes.
+ * Each field is a component of the estimated hard iron calibration.
+ * The values are in micro-Tesla (uT).
+ * </p>
+ * <p> Hard iron - These distortions arise due to the magnetized iron, steel or permanenet
+ * magnets on the device.
+ * Soft iron - These distortions arise due to the interaction with the earth's magentic
+ * field.
+ * </p>
+ * <h4> {@link android.hardware.Sensor#TYPE_GAME_ROTATION_VECTOR}:</h4>
+ * Identical to {@link android.hardware.Sensor#TYPE_ROTATION_VECTOR} except that it
+ * doesn't use the geomagnetic field. Therefore the Y axis doesn't
+ * point north, but instead to some other reference, that reference is
+ * allowed to drift by the same order of magnitude as the gyroscope
+ * drift around the Z axis.
+ * <p>
+ * In the ideal case, a phone rotated and returning to the same real-world
+ * orientation will report the same game rotation vector
+ * (without using the earth's geomagnetic field). However, the orientation
+ * may drift somewhat over time. See {@link android.hardware.Sensor#TYPE_ROTATION_VECTOR}
+ * for a detailed description of the values. This sensor will not have
+ * the estimated heading accuracy value.
+ * </p>
+ *
+ * <h4> {@link android.hardware.Sensor#TYPE_GYROSCOPE_UNCALIBRATED
+ * Sensor.TYPE_GYROSCOPE_UNCALIBRATED}:</h4>
+ * All values are in radians/second and measure the rate of rotation
+ * around the X, Y and Z axis. An estimation of the drift on each axis is
+ * reported as well.
+ * <p>
+ * No gyro-drift compensation is performed. Factory calibration and temperature
+ * compensation is still applied to the rate of rotation (angular speeds).
+ * </p>
+ * <p>
+ * The coordinate system is the same as is used for the
+ * {@link android.hardware.Sensor#TYPE_ACCELEROMETER}
+ * Rotation is positive in the counter-clockwise direction (right-hand rule).
+ * That is, an observer looking from some positive location on the x, y or z axis
+ * at a device positioned on the origin would report positive rotation if the device
+ * appeared to be rotating counter clockwise.
+ * The range would at least be 17.45 rad/s (ie: ~1000 deg/s).
+ * <ul>
+ * <li> values[0] : angular speed (w/o drift compensation) around the X axis in rad/s </li>
+ * <li> values[1] : angular speed (w/o drift compensation) around the Y axis in rad/s </li>
+ * <li> values[2] : angular speed (w/o drift compensation) around the Z axis in rad/s </li>
+ * <li> values[3] : estimated drift around X axis in rad/s </li>
+ * <li> values[4] : estimated drift around Y axis in rad/s </li>
+ * <li> values[5] : estimated drift around Z axis in rad/s </li>
* </ul>
+ * </p>
+ * <p><b>Pro Tip:</b> Always use the length of the values array while performing operations
+ * on it. In earlier versions, this used to be always 3 which has changed now. </p>
*
- * @see SensorEvent
* @see GeomagneticField
*/
-
public final float[] values;
/**
* The sensor that generated this event. See
* {@link android.hardware.SensorManager SensorManager} for details.
*/
- public Sensor sensor;
+ public Sensor sensor;
/**
* The accuracy of this event. See {@link android.hardware.SensorManager
@@ -445,14 +496,12 @@ public class SensorEvent {
*/
public int accuracy;
-
/**
* The time in nanosecond at which the event happened
*/
public long timestamp;
-
- SensorEvent(int size) {
- values = new float[size];
+ SensorEvent(int valueSize) {
+ values = new float[valueSize];
}
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 08fba29..30118f9 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -38,7 +38,11 @@ import java.util.List;
* hours. Note that the system will <i>not</i> disable sensors automatically when
* the screen turns off.
* </p>
- *
+ * <p class="note">
+ * Note: Don't use this mechanism with a Trigger Sensor, have a look
+ * at {@link TriggerEventListener}. {@link Sensor#TYPE_SIGNIFICANT_MOTION}
+ * is an example of a trigger sensor.
+ * </p>
* <pre class="prettyprint">
* public class SensorActivity extends Activity, implements SensorEventListener {
* private final SensorManager mSensorManager;
@@ -515,6 +519,12 @@ public abstract class SensorManager {
/**
* Unregisters a listener for the sensors with which it is registered.
*
+ * <p class="note"></p>
+ * Note: Don't use this method with a one shot trigger sensor such as
+ * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
+ * Use {@link #cancelTriggerSensor(TriggerEventListener, Sensor)} instead.
+ * </p>
+ *
* @param listener
* a SensorEventListener object
*
@@ -524,6 +534,7 @@ public abstract class SensorManager {
* @see #unregisterListener(SensorEventListener)
* @see #registerListener(SensorEventListener, Sensor, int)
*
+ * @throws IllegalArgumentException when sensor is a trigger sensor.
*/
public void unregisterListener(SensorEventListener listener, Sensor sensor) {
if (listener == null || sensor == null) {
@@ -558,6 +569,12 @@ public abstract class SensorManager {
* Registers a {@link android.hardware.SensorEventListener
* SensorEventListener} for the given sensor.
*
+ * <p class="note"></p>
+ * Note: Don't use this method with a one shot trigger sensor such as
+ * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
+ * Use {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead.
+ * </p>
+ *
* @param listener
* A {@link android.hardware.SensorEventListener SensorEventListener}
* object.
@@ -584,6 +601,7 @@ public abstract class SensorManager {
* @see #unregisterListener(SensorEventListener)
* @see #unregisterListener(SensorEventListener, Sensor)
*
+ * @throws IllegalArgumentException when sensor is null or a trigger sensor
*/
public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate) {
return registerListener(listener, sensor, rate, null);
@@ -593,6 +611,12 @@ public abstract class SensorManager {
* Registers a {@link android.hardware.SensorEventListener
* SensorEventListener} for the given sensor.
*
+ * <p class="note"></p>
+ * Note: Don't use this method with a one shot trigger sensor such as
+ * {@link Sensor#TYPE_SIGNIFICANT_MOTION}.
+ * Use {@link #requestTriggerSensor(TriggerEventListener, Sensor)} instead.
+ * </p>
+ *
* @param listener
* A {@link android.hardware.SensorEventListener SensorEventListener}
* object.
@@ -623,6 +647,7 @@ public abstract class SensorManager {
* @see #unregisterListener(SensorEventListener)
* @see #unregisterListener(SensorEventListener, Sensor)
*
+ * @throws IllegalArgumentException when sensor is null or a trigger sensor
*/
public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate,
Handler handler) {
@@ -1310,66 +1335,76 @@ public abstract class SensorManager {
Q[3] = rv[2];
}
- private LegacySensorManager getLegacySensorManager() {
- synchronized (mSensorListByType) {
- if (mLegacySensorManager == null) {
- Log.i(TAG, "This application is using deprecated SensorManager API which will "
- + "be removed someday. Please consider switching to the new API.");
- mLegacySensorManager = new LegacySensorManager(this);
- }
- return mLegacySensorManager;
- }
+ /**
+ * Requests receiving trigger events for a trigger sensor.
+ *
+ * <p>
+ * When the sensor detects a trigger event condition, such as significant motion in
+ * the case of the {@link Sensor#TYPE_SIGNIFICANT_MOTION}, the provided trigger listener
+ * will be invoked once and then its request to receive trigger events will be canceled.
+ * To continue receiving trigger events, the application must request to receive trigger
+ * events again.
+ * </p>
+ *
+ * @param listener The listener on which the
+ * {@link TriggerEventListener#onTrigger(TriggerEvent)} will be delivered.
+ * @param sensor The sensor to be enabled.
+ *
+ * @return true if the sensor was successfully enabled.
+ *
+ * @throws IllegalArgumentException when sensor is null or not a trigger sensor.
+ */
+ public boolean requestTriggerSensor(TriggerEventListener listener, Sensor sensor) {
+ return requestTriggerSensorImpl(listener, sensor);
}
/**
- * Sensor event pool implementation.
* @hide
*/
- protected static final class SensorEventPool {
- private final int mPoolSize;
- private final SensorEvent mPool[];
- private int mNumItemsInPool;
-
- private SensorEvent createSensorEvent() {
- // maximal size for all legacy events is 3
- return new SensorEvent(3);
- }
+ protected abstract boolean requestTriggerSensorImpl(TriggerEventListener listener,
+ Sensor sensor);
- SensorEventPool(int poolSize) {
- mPoolSize = poolSize;
- mNumItemsInPool = poolSize;
- mPool = new SensorEvent[poolSize];
- }
+ /**
+ * Cancels receiving trigger events for a trigger sensor.
+ *
+ * <p>
+ * Note that a Trigger sensor will be auto disabled if
+ * {@link TriggerEventListener#onTrigger(TriggerEvent)} has triggered.
+ * This method is provided in case the user wants to explicitly cancel the request
+ * to receive trigger events.
+ * </p>
+ *
+ * @param listener The listener on which the
+ * {@link TriggerEventListener#onTrigger(TriggerEvent)}
+ * is delivered.It should be the same as the one used
+ * in {@link #requestTriggerSensor(TriggerEventListener, Sensor)}
+ * @param sensor The sensor for which the trigger request should be canceled.
+ * If null, it cancels receiving trigger for all sensors associated
+ * with the listener.
+ *
+ * @return true if successfully canceled.
+ *
+ * @throws IllegalArgumentException when sensor is a trigger sensor.
+ */
+ public boolean cancelTriggerSensor(TriggerEventListener listener, Sensor sensor) {
+ return cancelTriggerSensorImpl(listener, sensor, true);
+ }
- SensorEvent getFromPool() {
- SensorEvent t = null;
- synchronized (this) {
- if (mNumItemsInPool > 0) {
- // remove the "top" item from the pool
- final int index = mPoolSize - mNumItemsInPool;
- t = mPool[index];
- mPool[index] = null;
- mNumItemsInPool--;
- }
- }
- if (t == null) {
- // the pool was empty or this item was removed from the pool for
- // the first time. In any case, we need to create a new item.
- t = createSensorEvent();
- }
- return t;
- }
+ /**
+ * @hide
+ */
+ protected abstract boolean cancelTriggerSensorImpl(TriggerEventListener listener,
+ Sensor sensor, boolean disable);
- void returnToPool(SensorEvent t) {
- synchronized (this) {
- // is there space left in the pool?
- if (mNumItemsInPool < mPoolSize) {
- // if so, return the item to the pool
- mNumItemsInPool++;
- final int index = mPoolSize - mNumItemsInPool;
- mPool[index] = t;
- }
+
+ private LegacySensorManager getLegacySensorManager() {
+ synchronized (mSensorListByType) {
+ if (mLegacySensorManager == null) {
+ Log.i(TAG, "This application is using deprecated SensorManager API which will "
+ + "be removed someday. Please consider switching to the new API.");
+ mLegacySensorManager = new LegacySensorManager(this);
}
+ return mLegacySensorManager;
}
}
}
diff --git a/core/java/android/hardware/SerialPort.java b/core/java/android/hardware/SerialPort.java
index 5ef122b..f50cdef 100644
--- a/core/java/android/hardware/SerialPort.java
+++ b/core/java/android/hardware/SerialPort.java
@@ -82,7 +82,9 @@ public class SerialPort {
}
/**
- * Reads data into the provided buffer
+ * Reads data into the provided buffer.
+ * Note that the value returned by {@link java.nio.Buffer#position()} on this buffer is
+ * unchanged after a call to this method.
*
* @param buffer to read into
* @return number of bytes read
@@ -98,7 +100,9 @@ public class SerialPort {
}
/**
- * Writes data from provided buffer
+ * Writes data from provided buffer.
+ * Note that the value returned by {@link java.nio.Buffer#position()} on this buffer is
+ * unchanged after a call to this method.
*
* @param buffer to write
* @param length number of bytes to write
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 7375e7d..852cf4a 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -1,4 +1,4 @@
-/*
+ /*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,16 +16,18 @@
package android.hardware;
-import android.os.Looper;
-import android.os.Process;
+import android.content.Context;
import android.os.Handler;
-import android.os.Message;
+import android.os.Looper;
+import android.os.MessageQueue;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import dalvik.system.CloseGuard;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
/**
@@ -35,236 +37,40 @@ import java.util.List;
* @hide
*/
public class SystemSensorManager extends SensorManager {
- private static final int SENSOR_DISABLE = -1;
- private static boolean sSensorModuleInitialized = false;
- private static ArrayList<Sensor> sFullSensorsList = new ArrayList<Sensor>();
- /* The thread and the sensor list are global to the process
- * but the actual thread is spawned on demand */
- private static SensorThread sSensorThread;
- private static int sQueue;
+ private static native void nativeClassInit();
+ private static native int nativeGetNextSensor(Sensor sensor, int next);
- // Used within this module from outside SensorManager, don't make private
- static SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>();
- static final ArrayList<ListenerDelegate> sListeners =
- new ArrayList<ListenerDelegate>();
+ private static boolean sSensorModuleInitialized = false;
+ private static final Object sSensorModuleLock = new Object();
+ private static final ArrayList<Sensor> sFullSensorsList = new ArrayList<Sensor>();
+ private static final SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>();
- // Common pool of sensor events.
- static SensorEventPool sPool;
+ // Listener list
+ private final HashMap<SensorEventListener, SensorEventQueue> mSensorListeners =
+ new HashMap<SensorEventListener, SensorEventQueue>();
+ private final HashMap<TriggerEventListener, TriggerEventQueue> mTriggerListeners =
+ new HashMap<TriggerEventListener, TriggerEventQueue>();
// Looper associated with the context in which this instance was created.
- final Looper mMainLooper;
-
- /*-----------------------------------------------------------------------*/
-
- static private class SensorThread {
-
- Thread mThread;
- boolean mSensorsReady;
-
- SensorThread() {
- }
-
- @Override
- protected void finalize() {
- }
-
- // must be called with sListeners lock
- boolean startLocked() {
- try {
- if (mThread == null) {
- mSensorsReady = false;
- SensorThreadRunnable runnable = new SensorThreadRunnable();
- Thread thread = new Thread(runnable, SensorThread.class.getName());
- thread.start();
- synchronized (runnable) {
- while (mSensorsReady == false) {
- runnable.wait();
- }
- }
- mThread = thread;
- }
- } catch (InterruptedException e) {
- }
- return mThread == null ? false : true;
- }
-
- private class SensorThreadRunnable implements Runnable {
- SensorThreadRunnable() {
- }
-
- private boolean open() {
- // NOTE: this cannot synchronize on sListeners, since
- // it's held in the main thread at least until we
- // return from here.
- sQueue = sensors_create_queue();
- return true;
- }
-
- public void run() {
- //Log.d(TAG, "entering main sensor thread");
- final float[] values = new float[3];
- final int[] status = new int[1];
- final long timestamp[] = new long[1];
- Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
-
- if (!open()) {
- return;
- }
+ private final Looper mMainLooper;
+ private final int mTargetSdkLevel;
- synchronized (this) {
- // we've open the driver, we're ready to open the sensors
- mSensorsReady = true;
- this.notify();
- }
-
- while (true) {
- // wait for an event
- final int sensor = sensors_data_poll(sQueue, values, status, timestamp);
-
- int accuracy = status[0];
- synchronized (sListeners) {
- if (sensor == -1 || sListeners.isEmpty()) {
- // we lost the connection to the event stream. this happens
- // when the last listener is removed or if there is an error
- if (sensor == -1 && !sListeners.isEmpty()) {
- // log a warning in case of abnormal termination
- Log.e(TAG, "_sensors_data_poll() failed, we bail out: sensors=" + sensor);
- }
- // we have no more listeners or polling failed, terminate the thread
- sensors_destroy_queue(sQueue);
- sQueue = 0;
- mThread = null;
- break;
- }
- final Sensor sensorObject = sHandleToSensor.get(sensor);
- if (sensorObject != null) {
- // report the sensor event to all listeners that
- // care about it.
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate listener = sListeners.get(i);
- if (listener.hasSensor(sensorObject)) {
- // this is asynchronous (okay to call
- // with sListeners lock held).
- listener.onSensorChangedLocked(sensorObject,
- values, timestamp, accuracy);
- }
- }
- }
- }
- }
- //Log.d(TAG, "exiting main sensor thread");
- }
- }
- }
-
- /*-----------------------------------------------------------------------*/
-
- private class ListenerDelegate {
- private final SensorEventListener mSensorEventListener;
- private final ArrayList<Sensor> mSensorList = new ArrayList<Sensor>();
- private final Handler mHandler;
- public SparseBooleanArray mSensors = new SparseBooleanArray();
- public SparseBooleanArray mFirstEvent = new SparseBooleanArray();
- public SparseIntArray mSensorAccuracies = new SparseIntArray();
-
- ListenerDelegate(SensorEventListener listener, Sensor sensor, Handler handler) {
- mSensorEventListener = listener;
- Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
- // currently we create one Handler instance per listener, but we could
- // have one per looper (we'd need to pass the ListenerDelegate
- // instance to handleMessage and keep track of them separately).
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- final SensorEvent t = (SensorEvent)msg.obj;
- final int handle = t.sensor.getHandle();
-
- switch (t.sensor.getType()) {
- // Only report accuracy for sensors that support it.
- case Sensor.TYPE_MAGNETIC_FIELD:
- case Sensor.TYPE_ORIENTATION:
- // call onAccuracyChanged() only if the value changes
- final int accuracy = mSensorAccuracies.get(handle);
- if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
- mSensorAccuracies.put(handle, t.accuracy);
- mSensorEventListener.onAccuracyChanged(t.sensor, t.accuracy);
- }
- break;
- default:
- // For other sensors, just report the accuracy once
- if (mFirstEvent.get(handle) == false) {
- mFirstEvent.put(handle, true);
- mSensorEventListener.onAccuracyChanged(
- t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
- }
- break;
- }
-
- mSensorEventListener.onSensorChanged(t);
- sPool.returnToPool(t);
- }
- };
- addSensor(sensor);
- }
-
- Object getListener() {
- return mSensorEventListener;
- }
-
- void addSensor(Sensor sensor) {
- mSensors.put(sensor.getHandle(), true);
- mSensorList.add(sensor);
- }
- int removeSensor(Sensor sensor) {
- mSensors.delete(sensor.getHandle());
- mSensorList.remove(sensor);
- return mSensors.size();
- }
- boolean hasSensor(Sensor sensor) {
- return mSensors.get(sensor.getHandle());
- }
- List<Sensor> getSensors() {
- return mSensorList;
- }
-
- void onSensorChangedLocked(Sensor sensor, float[] values, long[] timestamp, int accuracy) {
- SensorEvent t = sPool.getFromPool();
- final float[] v = t.values;
- v[0] = values[0];
- v[1] = values[1];
- v[2] = values[2];
- t.timestamp = timestamp[0];
- t.accuracy = accuracy;
- t.sensor = sensor;
- Message msg = Message.obtain();
- msg.what = 0;
- msg.obj = t;
- msg.setAsynchronous(true);
- mHandler.sendMessage(msg);
- }
- }
-
- /**
- * {@hide}
- */
- public SystemSensorManager(Looper mainLooper) {
+ /** {@hide} */
+ public SystemSensorManager(Context context, Looper mainLooper) {
mMainLooper = mainLooper;
-
- synchronized(sListeners) {
+ mTargetSdkLevel = context.getApplicationInfo().targetSdkVersion;
+ synchronized(sSensorModuleLock) {
if (!sSensorModuleInitialized) {
sSensorModuleInitialized = true;
nativeClassInit();
// initialize the sensor list
- sensors_module_init();
final ArrayList<Sensor> fullList = sFullSensorsList;
int i = 0;
do {
Sensor sensor = new Sensor();
- i = sensors_module_get_next_sensor(sensor, i);
-
+ i = nativeGetNextSensor(sensor, i);
if (i>=0) {
//Log.d(TAG, "found sensor: " + sensor.getName() +
// ", handle=" + sensor.getHandle());
@@ -272,128 +78,342 @@ public class SystemSensorManager extends SensorManager {
sHandleToSensor.append(sensor.getHandle(), sensor);
}
} while (i>0);
-
- sPool = new SensorEventPool( sFullSensorsList.size()*2 );
- sSensorThread = new SensorThread();
}
}
}
+
/** @hide */
@Override
protected List<Sensor> getFullSensorList() {
return sFullSensorsList;
}
- private boolean enableSensorLocked(Sensor sensor, int delay) {
- boolean result = false;
- for (ListenerDelegate i : sListeners) {
- if (i.hasSensor(sensor)) {
- String name = sensor.getName();
- int handle = sensor.getHandle();
- result = sensors_enable_sensor(sQueue, name, handle, delay);
- break;
+
+ /** @hide */
+ @Override
+ protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
+ int delay, Handler handler)
+ {
+ // Invariants to preserve:
+ // - one Looper per SensorEventListener
+ // - one Looper per SensorEventQueue
+ // We map SensorEventListener to a SensorEventQueue, which holds the looper
+ if (sensor == null) throw new IllegalArgumentException("sensor cannot be null");
+
+ // Trigger Sensors should use the requestTriggerSensor call.
+ if (Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) return false;
+
+ synchronized (mSensorListeners) {
+ SensorEventQueue queue = mSensorListeners.get(listener);
+ if (queue == null) {
+ Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
+ queue = new SensorEventQueue(listener, looper, this);
+ if (!queue.addSensor(sensor, delay)) {
+ queue.dispose();
+ return false;
+ }
+ mSensorListeners.put(listener, queue);
+ return true;
+ } else {
+ return queue.addSensor(sensor, delay);
}
}
- return result;
}
- private boolean disableSensorLocked(Sensor sensor) {
- for (ListenerDelegate i : sListeners) {
- if (i.hasSensor(sensor)) {
- // not an error, it's just that this sensor is still in use
+ /** @hide */
+ @Override
+ protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
+ // Trigger Sensors should use the cancelTriggerSensor call.
+ if (sensor != null && Sensor.getReportingMode(sensor) == Sensor.REPORTING_MODE_ONE_SHOT) {
+ return;
+ }
+
+ synchronized (mSensorListeners) {
+ SensorEventQueue queue = mSensorListeners.get(listener);
+ if (queue != null) {
+ boolean result;
+ if (sensor == null) {
+ result = queue.removeAllSensors();
+ } else {
+ result = queue.removeSensor(sensor, true);
+ }
+ if (result && !queue.hasSensors()) {
+ mSensorListeners.remove(listener);
+ queue.dispose();
+ }
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+ if (sensor == null) throw new IllegalArgumentException("sensor cannot be null");
+
+ if (Sensor.getReportingMode(sensor) != Sensor.REPORTING_MODE_ONE_SHOT) return false;
+
+ synchronized (mTriggerListeners) {
+ TriggerEventQueue queue = mTriggerListeners.get(listener);
+ if (queue == null) {
+ queue = new TriggerEventQueue(listener, mMainLooper, this);
+ if (!queue.addSensor(sensor, 0)) {
+ queue.dispose();
+ return false;
+ }
+ mTriggerListeners.put(listener, queue);
return true;
+ } else {
+ return queue.addSensor(sensor, 0);
}
}
- String name = sensor.getName();
- int handle = sensor.getHandle();
- return sensors_enable_sensor(sQueue, name, handle, SENSOR_DISABLE);
}
/** @hide */
@Override
- protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
- int delay, Handler handler) {
- boolean result = true;
- synchronized (sListeners) {
- // look for this listener in our list
- ListenerDelegate l = null;
- for (ListenerDelegate i : sListeners) {
- if (i.getListener() == listener) {
- l = i;
- break;
+ protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor,
+ boolean disable) {
+ if (sensor != null && Sensor.getReportingMode(sensor) != Sensor.REPORTING_MODE_ONE_SHOT) {
+ return false;
+ }
+ synchronized (mTriggerListeners) {
+ TriggerEventQueue queue = mTriggerListeners.get(listener);
+ if (queue != null) {
+ boolean result;
+ if (sensor == null) {
+ result = queue.removeAllSensors();
+ } else {
+ result = queue.removeSensor(sensor, disable);
+ }
+ if (result && !queue.hasSensors()) {
+ mTriggerListeners.remove(listener);
+ queue.dispose();
}
+ return result;
}
+ return false;
+ }
+ }
+
+ /*
+ * BaseEventQueue is the communication channel with the sensor service,
+ * SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between
+ * the queues and the listeners.
+ */
+ private static abstract class BaseEventQueue {
+ private native int nativeInitBaseEventQueue(BaseEventQueue eventQ, MessageQueue msgQ,
+
+ float[] scratch);
+ private static native int nativeEnableSensor(int eventQ, int handle, int us);
+ private static native int nativeDisableSensor(int eventQ, int handle);
+ private static native void nativeDestroySensorEventQueue(int eventQ);
+ private int nSensorEventQueue;
+ private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
+ protected final SparseIntArray mSensorAccuracies = new SparseIntArray();
+ protected final SparseBooleanArray mFirstEvent = new SparseBooleanArray();
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final float[] mScratch = new float[16];
+ protected final SystemSensorManager mManager;
+
+ BaseEventQueue(Looper looper, SystemSensorManager manager) {
+ nSensorEventQueue = nativeInitBaseEventQueue(this, looper.getQueue(), mScratch);
+ mCloseGuard.open("dispose");
+ mManager = manager;
+ }
+
+ public void dispose() {
+ dispose(false);
+ }
- // if we don't find it, add it to the list
- if (l == null) {
- l = new ListenerDelegate(listener, sensor, handler);
- sListeners.add(l);
- // if the list is not empty, start our main thread
- if (!sListeners.isEmpty()) {
- if (sSensorThread.startLocked()) {
- if (!enableSensorLocked(sensor, delay)) {
- // oops. there was an error
- sListeners.remove(l);
- result = false;
- }
+ public boolean addSensor(Sensor sensor, int delay) {
+ // Check if already present.
+ int handle = sensor.getHandle();
+ if (mActiveSensors.get(handle)) return false;
+
+ // Get ready to receive events before calling enable.
+ mActiveSensors.put(handle, true);
+ addSensorEvent(sensor);
+ if (enableSensor(sensor, delay) != 0) {
+ removeSensor(sensor, false);
+ return false;
+ }
+ return true;
+ }
+
+ public boolean removeAllSensors() {
+ for (int i=0 ; i<mActiveSensors.size(); i++) {
+ if (mActiveSensors.valueAt(i) == true) {
+ int handle = mActiveSensors.keyAt(i);
+ Sensor sensor = sHandleToSensor.get(handle);
+ if (sensor != null) {
+ disableSensor(sensor);
+ mActiveSensors.put(handle, false);
+ removeSensorEvent(sensor);
} else {
- // there was an error, remove the listener
- sListeners.remove(l);
- result = false;
+ // it should never happen -- just ignore.
}
- } else {
- // weird, we couldn't add the listener
- result = false;
}
- } else if (!l.hasSensor(sensor)) {
- l.addSensor(sensor);
- if (!enableSensorLocked(sensor, delay)) {
- // oops. there was an error
- l.removeSensor(sensor);
- result = false;
+ }
+ return true;
+ }
+
+ public boolean removeSensor(Sensor sensor, boolean disable) {
+ final int handle = sensor.getHandle();
+ if (mActiveSensors.get(handle)) {
+ if (disable) disableSensor(sensor);
+ mActiveSensors.put(sensor.getHandle(), false);
+ removeSensorEvent(sensor);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean hasSensors() {
+ // no more sensors are set
+ return mActiveSensors.indexOfValue(true) >= 0;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void dispose(boolean finalized) {
+ if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
}
+ mCloseGuard.close();
+ }
+ if (nSensorEventQueue != 0) {
+ nativeDestroySensorEventQueue(nSensorEventQueue);
+ nSensorEventQueue = 0;
}
}
- return result;
+ private int enableSensor(Sensor sensor, int us) {
+ if (nSensorEventQueue == 0) throw new NullPointerException();
+ if (sensor == null) throw new NullPointerException();
+ return nativeEnableSensor(nSensorEventQueue, sensor.getHandle(), us);
+ }
+ private int disableSensor(Sensor sensor) {
+ if (nSensorEventQueue == 0) throw new NullPointerException();
+ if (sensor == null) throw new NullPointerException();
+ return nativeDisableSensor(nSensorEventQueue, sensor.getHandle());
+ }
+ protected abstract void dispatchSensorEvent(int handle, float[] values, int accuracy,
+ long timestamp);
+
+ protected abstract void addSensorEvent(Sensor sensor);
+ protected abstract void removeSensorEvent(Sensor sensor);
}
- /** @hide */
- @Override
- protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
- synchronized (sListeners) {
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate l = sListeners.get(i);
- if (l.getListener() == listener) {
- if (sensor == null) {
- sListeners.remove(i);
- // disable all sensors for this listener
- for (Sensor s : l.getSensors()) {
- disableSensorLocked(s);
- }
- // Check if the ListenerDelegate has the sensor it is trying to unregister.
- } else if (l.hasSensor(sensor) && l.removeSensor(sensor) == 0) {
- // if we have no more sensors enabled on this listener,
- // take it off the list.
- sListeners.remove(i);
- disableSensorLocked(sensor);
+ static final class SensorEventQueue extends BaseEventQueue {
+ private final SensorEventListener mListener;
+ private final SparseArray<SensorEvent> mSensorsEvents = new SparseArray<SensorEvent>();
+
+ public SensorEventQueue(SensorEventListener listener, Looper looper,
+ SystemSensorManager manager) {
+ super(looper, manager);
+ mListener = listener;
+ }
+
+ public void addSensorEvent(Sensor sensor) {
+ SensorEvent t = new SensorEvent(Sensor.getMaxLengthValuesArray(sensor,
+ mManager.mTargetSdkLevel));
+ mSensorsEvents.put(sensor.getHandle(), t);
+ }
+
+ public void removeSensorEvent(Sensor sensor) {
+ mSensorsEvents.delete(sensor.getHandle());
+ }
+
+ // Called from native code.
+ @SuppressWarnings("unused")
+ @Override
+ protected void dispatchSensorEvent(int handle, float[] values, int inAccuracy,
+ long timestamp) {
+ final Sensor sensor = sHandleToSensor.get(handle);
+ SensorEvent t = mSensorsEvents.get(handle);
+ if (t == null) {
+ Log.e(TAG, "Error: Sensor Event is null for Sensor: " + sensor);
+ return;
+ }
+ // Copy from the values array.
+ System.arraycopy(values, 0, t.values, 0, t.values.length);
+ t.timestamp = timestamp;
+ t.accuracy = inAccuracy;
+ t.sensor = sensor;
+ switch (t.sensor.getType()) {
+ // Only report accuracy for sensors that support it.
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ case Sensor.TYPE_ORIENTATION:
+ // call onAccuracyChanged() only if the value changes
+ final int accuracy = mSensorAccuracies.get(handle);
+ if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
+ mSensorAccuracies.put(handle, t.accuracy);
+ mListener.onAccuracyChanged(t.sensor, t.accuracy);
+ }
+ break;
+ default:
+ // For other sensors, just report the accuracy once
+ if (mFirstEvent.get(handle) == false) {
+ mFirstEvent.put(handle, true);
+ mListener.onAccuracyChanged(
+ t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
}
break;
- }
}
+ mListener.onSensorChanged(t);
}
}
- private static native void nativeClassInit();
+ static final class TriggerEventQueue extends BaseEventQueue {
+ private final TriggerEventListener mListener;
+ private final SparseArray<TriggerEvent> mTriggerEvents = new SparseArray<TriggerEvent>();
- private static native int sensors_module_init();
- private static native int sensors_module_get_next_sensor(Sensor sensor, int next);
+ public TriggerEventQueue(TriggerEventListener listener, Looper looper,
+ SystemSensorManager manager) {
+ super(looper, manager);
+ mListener = listener;
+ }
+
+ public void addSensorEvent(Sensor sensor) {
+ TriggerEvent t = new TriggerEvent(Sensor.getMaxLengthValuesArray(sensor,
+ mManager.mTargetSdkLevel));
+ mTriggerEvents.put(sensor.getHandle(), t);
+ }
+
+ public void removeSensorEvent(Sensor sensor) {
+ mTriggerEvents.delete(sensor.getHandle());
+ }
- // Used within this module from outside SensorManager, don't make private
- static native int sensors_create_queue();
- static native void sensors_destroy_queue(int queue);
- static native boolean sensors_enable_sensor(int queue, String name, int sensor, int enable);
- static native int sensors_data_poll(int queue, float[] values, int[] status, long[] timestamp);
+ // Called from native code.
+ @SuppressWarnings("unused")
+ @Override
+ protected void dispatchSensorEvent(int handle, float[] values, int accuracy,
+ long timestamp) {
+ final Sensor sensor = sHandleToSensor.get(handle);
+ TriggerEvent t = mTriggerEvents.get(handle);
+ if (t == null) {
+ Log.e(TAG, "Error: Trigger Event is null for Sensor: " + sensor);
+ return;
+ }
+
+ // Copy from the values array.
+ System.arraycopy(values, 0, t.values, 0, t.values.length);
+ t.timestamp = timestamp;
+ t.sensor = sensor;
+
+ // A trigger sensor is auto disabled. So just clean up and don't call native
+ // disable.
+ mManager.cancelTriggerSensorImpl(mListener, sensor, false);
+
+ mListener.onTrigger(t);
+ }
+ }
}
diff --git a/core/java/android/hardware/TriggerEvent.java b/core/java/android/hardware/TriggerEvent.java
new file mode 100644
index 0000000..bdd39f3
--- /dev/null
+++ b/core/java/android/hardware/TriggerEvent.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+/**
+ * This class represents a Trigger Event - the event
+ * associated with a Trigger Sensor. When the sensor detects a trigger
+ * event condition, such as significant motion in the case of the
+ * {@link Sensor#TYPE_SIGNIFICANT_MOTION}, the {@link TriggerEventListener}
+ * is called with the TriggerEvent. The sensor is automatically canceled
+ * after the trigger.
+ * <p>
+ * This class holds information such as the value of the sensor
+ * when the trigger happened, the timestamp along with detailed
+ * information regarding the Sensor itself.
+ * </p>
+ * @see android.hardware.SensorManager
+ * @see android.hardware.TriggerEvent
+ * @see android.hardware.Sensor
+ */
+public final class TriggerEvent {
+ /**
+ * <p>
+ * The length and contents of the {@link #values values} array depends on
+ * which {@link android.hardware.Sensor sensor} type is being monitored (see
+ * also {@link SensorEvent} for a definition of the coordinate system used).
+ * </p>
+ * <h4> {@link Sensor#TYPE_SIGNIFICANT_MOTION} </h4>
+ * The value field is of length 1. value[0] = 1.0 when the sensor triggers.
+ * 1.0 is the only allowed value.
+ */
+ public final float[] values;
+
+ /**
+ * The sensor that generated this event. See
+ * {@link android.hardware.SensorManager SensorManager} for details.
+ */
+ public Sensor sensor;
+
+ /**
+ * The time in nanosecond at which the event happened
+ */
+ public long timestamp;
+
+ TriggerEvent(int size) {
+ values = new float[size];
+ }
+}
diff --git a/core/java/android/hardware/TriggerEventListener.java b/core/java/android/hardware/TriggerEventListener.java
new file mode 100644
index 0000000..8fa9702
--- /dev/null
+++ b/core/java/android/hardware/TriggerEventListener.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware;
+
+/**
+ * This class is the listener used to handle Trigger Sensors.
+ * Trigger Sensors are sensors that trigger an event and are automatically
+ * disabled. {@link Sensor#TYPE_SIGNIFICANT_MOTION} is one such example.
+ * <p>
+ * {@link SensorManager} lets you access the device's {@link android.hardware.Sensor
+ * sensors}. Get an instance of {@link SensorManager} by calling
+ * {@link android.content.Context#getSystemService(java.lang.String)
+ * Context.getSystemService()} with the argument
+ * {@link android.content.Context#SENSOR_SERVICE}.
+ * <p>Here's an example setup for a TriggerEventListener:
+ *
+ * <pre>
+ * class TriggerListener extends TriggerEventListener {
+ * public void onTrigger(TriggerEvent event) {
+ * // Do Work.
+ *
+ * // As it is a one shot sensor, it will be canceled automatically.
+ * // SensorManager.requestTriggerSensor(this, mSigMotion); needs to
+ * // be called again, if needed.
+ * }
+ * }
+ * public class SensorActivity extends Activity {
+ * private final SensorManager mSensorManager;
+ * private final Sensor mSigMotion;
+ * private final TriggerEventListener mListener = new TriggerEventListener();
+ *
+ * public SensorActivity() {
+ * mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
+ * mSigMotion = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+ * }
+ *
+ * protected void onResume() {
+ * super.onResume();
+ * mSensorManager.requestTriggerSensor(mListener, mSigMotion);
+ * }
+ *
+ * protected void onPause() {
+ * super.onPause();
+ * // Call disable to ensure that the trigger request has been canceled.
+ * mSensorManager.cancelTriggerSensor(mListener, mSigMotion);
+ * }
+ *
+ * }
+ * </pre>
+ *
+ * @see TriggerEvent
+ * @see Sensor
+ */
+public abstract class TriggerEventListener {
+ /**
+ * The method that will be called when the sensor
+ * is triggered. Override this method in your implementation
+ * of this class.
+ *
+ * @param event The details of the event.
+ */
+ public abstract void onTrigger(TriggerEvent event);
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 262d87d..761faaf 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -212,8 +212,10 @@ public final class InputManager {
} catch (RemoteException ex) {
throw new RuntimeException("Could not get input device information.", ex);
}
+ if (inputDevice != null) {
+ mInputDevices.setValueAt(index, inputDevice);
+ }
}
- mInputDevices.setValueAt(index, inputDevice);
return inputDevice;
}
}
@@ -241,6 +243,8 @@ public final class InputManager {
inputDevice = mIm.getInputDevice(id);
} catch (RemoteException ex) {
// Ignore the problem for the purposes of this method.
+ }
+ if (inputDevice == null) {
continue;
}
mInputDevices.setValueAt(i, inputDevice);
@@ -809,6 +813,22 @@ public final class InputManager {
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public void vibrate(int owningUid, String owningPackage, long milliseconds) {
+ vibrate(milliseconds);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
+ vibrate(pattern, repeat);
+ }
+
@Override
public void cancel() {
try {
diff --git a/core/java/android/hardware/location/GeofenceHardware.java b/core/java/android/hardware/location/GeofenceHardware.java
new file mode 100644
index 0000000..e67d0d7
--- /dev/null
+++ b/core/java/android/hardware/location/GeofenceHardware.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.location;
+
+import android.content.Context;
+import android.location.Location;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * This class handles geofences managed by various hardware subsystems. It contains
+ * the public APIs that is needed to accomplish the task.
+ *
+ * <p>The APIs should not be called directly by the app developers. A higher level api
+ * which abstracts the hardware should be used instead. All the checks are done by the higher
+ * level public API. Any needed locking should be handled by the higher level API.
+ *
+ * <p> There are 3 states associated with a Geofence: Inside, Outside, Unknown.
+ * There are 3 transitions: {@link #GEOFENCE_ENTERED}, {@link #GEOFENCE_EXITED},
+ * {@link #GEOFENCE_UNCERTAIN}. The APIs only expose the transitions.
+ *
+ * <p> Inside state: The hardware subsystem is reasonably confident that the user is inside
+ * the geofence. Outside state: The hardware subsystem is reasonably confident that the user
+ * is outside the geofence Unknown state: Unknown state can be interpreted as a state in which the
+ * monitoring subsystem isn't confident enough that the user is either inside or
+ * outside the Geofence. If the accuracy does not improve for a sufficient period of time,
+ * the {@link #GEOFENCE_UNCERTAIN} transition would be triggered. If the accuracy improves later,
+ * an appropriate transition would be triggered. The "reasonably confident" parameter
+ * depends on the hardware system and the positioning algorithms used.
+ * For instance, {@link #MONITORING_TYPE_GPS_HARDWARE} uses 95% as a confidence level.
+ */
+public final class GeofenceHardware {
+ private IGeofenceHardware mService;
+
+ // Hardware systems that do geofence monitoring.
+ static final int NUM_MONITORS = 1;
+
+ /**
+ * Constant for geofence monitoring done by the GPS hardware.
+ */
+ public static final int MONITORING_TYPE_GPS_HARDWARE = 0;
+
+ /**
+ * Constant to indiciate that the monitoring system is currently
+ * available for monitoring geofences.
+ */
+ public static final int MONITOR_CURRENTLY_AVAILABLE = 0;
+
+ /**
+ * Constant to indiciate that the monitoring system is currently
+ * unavailable for monitoring geofences.
+ */
+ public static final int MONITOR_CURRENTLY_UNAVAILABLE = 1;
+
+ /**
+ * Constant to indiciate that the monitoring system is unsupported
+ * for hardware geofence monitoring.
+ */
+ public static final int MONITOR_UNSUPPORTED = 2;
+
+ // The following constants need to match geofence flags in gps.h
+ /**
+ * The constant to indicate that the user has entered the geofence.
+ */
+ public static final int GEOFENCE_ENTERED = 1<<0L;
+
+ /**
+ * The constant to indicate that the user has exited the geofence.
+ */
+ public static final int GEOFENCE_EXITED = 1<<1L;
+
+ /**
+ * The constant to indicate that the user is uncertain with respect to a
+ * geofence. nn
+ */
+ public static final int GEOFENCE_UNCERTAIN = 1<<2L;
+
+ /**
+ * The constant used to indicate success of the particular geofence call
+ */
+ public static final int GEOFENCE_SUCCESS = 0;
+
+ /**
+ * The constant used to indicate that too many geofences have been registered.
+ */
+ public static final int GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 1;
+
+ /**
+ * The constant used to indicate that the geofence id already exists.
+ */
+ public static final int GEOFENCE_ERROR_ID_EXISTS = 2;
+
+ /**
+ * The constant used to indicate that the geofence id is unknown.
+ */
+ public static final int GEOFENCE_ERROR_ID_UNKNOWN = 3;
+
+ /**
+ * The constant used to indicate that the transition requested for the geofence is invalid.
+ */
+ public static final int GEOFENCE_ERROR_INVALID_TRANSITION = 4;
+
+ /**
+ * The constant used to indicate that the geofence operation has failed.
+ */
+ public static final int GEOFENCE_FAILURE = 5;
+
+ static final int GPS_GEOFENCE_UNAVAILABLE = 1<<0L;
+ static final int GPS_GEOFENCE_AVAILABLE = 1<<1L;
+
+ private HashMap<GeofenceHardwareCallback, GeofenceHardwareCallbackWrapper>
+ mCallbacks = new HashMap<GeofenceHardwareCallback, GeofenceHardwareCallbackWrapper>();
+ private HashMap<GeofenceHardwareMonitorCallback, GeofenceHardwareMonitorCallbackWrapper>
+ mMonitorCallbacks = new HashMap<GeofenceHardwareMonitorCallback,
+ GeofenceHardwareMonitorCallbackWrapper>();
+ /**
+ * @hide
+ */
+ public GeofenceHardware(IGeofenceHardware service) {
+ mService = service;
+ }
+
+ /**
+ * Returns all the hardware geofence monitoring systems which are supported
+ *
+ * <p> Call {@link #getStatusOfMonitoringType(int)} to know the current state
+ * of a monitoring system.
+ *
+ * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
+ * geofencing in hardware.
+ *
+ * @return An array of all the monitoring types.
+ * An array of length 0 is returned in case of errors.
+ */
+ public int[] getMonitoringTypes() {
+ try {
+ return mService.getMonitoringTypes();
+ } catch (RemoteException e) {
+ }
+ return new int[0];
+ }
+
+ /**
+ * Returns current status of a hardware geofence monitoring system.
+ *
+ * <p>Status can be one of {@link #MONITOR_CURRENTLY_AVAILABLE},
+ * {@link #MONITOR_CURRENTLY_UNAVAILABLE} or {@link #MONITOR_UNSUPPORTED}
+ *
+ * <p> Some supported hardware monitoring systems might not be available
+ * for monitoring geofences in certain scenarios. For example, when a user
+ * enters a building, the GPS hardware subsystem might not be able monitor
+ * geofences and will change from {@link #MONITOR_CURRENTLY_AVAILABLE} to
+ * {@link #MONITOR_CURRENTLY_UNAVAILABLE}.
+ *
+ * @param monitoringType
+ * @return Current status of the monitoring type.
+ */
+ public int getStatusOfMonitoringType(int monitoringType) {
+ try {
+ return mService.getStatusOfMonitoringType(monitoringType);
+ } catch (RemoteException e) {
+ return MONITOR_UNSUPPORTED;
+ }
+ }
+
+ /**
+ * Creates a circular geofence which is monitored by subsystems in the hardware.
+ *
+ * <p> When the device detects that is has entered, exited or is uncertain
+ * about the area specified by the geofence, the given callback will be called.
+ *
+ * <p> If this call returns true, it means that the geofence has been sent to the hardware.
+ * {@link GeofenceHardwareCallback#onGeofenceAdd} will be called with the result of the
+ * add call from the hardware. The {@link GeofenceHardwareCallback#onGeofenceAdd} will be
+ * called with the following parameters when a transition event occurs.
+ * <ul>
+ * <li> The geofence Id
+ * <li> The location object indicating the last known location.
+ * <li> The transition associated with the geofence. One of
+ * {@link #GEOFENCE_ENTERED}, {@link #GEOFENCE_EXITED}, {@link #GEOFENCE_UNCERTAIN}
+ * <li> The timestamp when the geofence transition occured.
+ * <li> The monitoring type ({@link #MONITORING_TYPE_GPS_HARDWARE} is one such example)
+ * that was used.
+ * </ul>
+ *
+ * <p> The geofence will be monitored by the subsystem specified by monitoring_type parameter.
+ * The application does not need to hold a wakelock when the monitoring
+ * is being done by the underlying hardware subsystem. If the same geofence Id is being
+ * monitored by two different monitoring systems, the same id can be used for both calls, as
+ * long as the same callback object is used.
+ *
+ * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when
+ * {@link #MONITORING_TYPE_GPS_HARDWARE} is used.
+ *
+ * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
+ * geofencing in hardware.
+ *
+ * <p>This API should not be called directly by the app developers. A higher level api
+ * which abstracts the hardware should be used instead. All the checks are done by the higher
+ * level public API. Any needed locking should be handled by the higher level API.
+ *
+ * <p> Create a geofence request object using the methods in {@link GeofenceHardwareRequest} to
+ * set all the characteristics of the geofence. Use the created GeofenceHardwareRequest object
+ * in this call.
+ *
+ * @param geofenceId The id associated with the geofence.
+ * @param monitoringType The type of the hardware subsystem that should be used
+ * to monitor the geofence.
+ * @param geofenceRequest The {@link GeofenceHardwareRequest} object associated with the
+ * geofence.
+ * @param callback {@link GeofenceHardwareCallback} that will be use to notify the
+ * transition.
+ * @return true when the geofence is successfully sent to the hardware for addition.
+ * @throws IllegalArgumentException when the geofence request type is not supported.
+ */
+ public boolean addGeofence(int geofenceId, int monitoringType, GeofenceHardwareRequest
+ geofenceRequest, GeofenceHardwareCallback callback) {
+ try {
+ if (geofenceRequest.getType() == GeofenceHardwareRequest.GEOFENCE_TYPE_CIRCLE) {
+ return mService.addCircularFence(geofenceId, monitoringType,
+ geofenceRequest.getLatitude(),
+ geofenceRequest.getLongitude(), geofenceRequest.getRadius(),
+ geofenceRequest.getLastTransition(),
+ geofenceRequest.getMonitorTransitions(),
+ geofenceRequest.getNotificationResponsiveness(),
+ geofenceRequest.getUnknownTimer(),
+ getCallbackWrapper(callback));
+ } else {
+ throw new IllegalArgumentException("Geofence Request type not supported");
+ }
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Removes a geofence added by {@link #addGeofence} call.
+ *
+ * <p> If this call returns true, it means that the geofence has been sent to the hardware.
+ * {@link GeofenceHardwareCallback#onGeofenceRemove} will be called with the result of the
+ * remove call from the hardware.
+ *
+ * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when
+ * {@link #MONITORING_TYPE_GPS_HARDWARE} is used.
+ *
+ * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
+ * geofencing in hardware.
+ *
+ * <p>This API should not be called directly by the app developers. A higher level api
+ * which abstracts the hardware should be used instead. All the checks are done by the higher
+ * level public API. Any needed locking should be handled by the higher level API.
+ *
+ * @param geofenceId The id of the geofence.
+ * @param monitoringType The type of the hardware subsystem that should be used
+ * to monitor the geofence.
+ * @return true when the geofence is successfully sent to the hardware for removal. .
+ */
+ public boolean removeGeofence(int geofenceId, int monitoringType) {
+ try {
+ return mService.removeGeofence(geofenceId, monitoringType);
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Pauses the monitoring of a geofence added by {@link #addGeofence} call.
+ *
+ * <p> If this call returns true, it means that the geofence has been sent to the hardware.
+ * {@link GeofenceHardwareCallback#onGeofencePause} will be called with the result of the
+ * pause call from the hardware.
+ *
+ * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when
+ * {@link #MONITORING_TYPE_GPS_HARDWARE} is used.
+ *
+ * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
+ * geofencing in hardware.
+ *
+ * <p>This API should not be called directly by the app developers. A higher level api
+ * which abstracts the hardware should be used instead. All the checks are done by the higher
+ * level public API. Any needed locking should be handled by the higher level API.
+ *
+ * @param geofenceId The id of the geofence.
+ * @param monitoringType The type of the hardware subsystem that should be used
+ * to monitor the geofence.
+ * @return true when the geofence is successfully sent to the hardware for pausing.
+ */
+ public boolean pauseGeofence(int geofenceId, int monitoringType) {
+ try {
+ return mService.pauseGeofence(geofenceId, monitoringType);
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Resumes the monitoring of a geofence added by {@link #pauseGeofence} call.
+ *
+ * <p> If this call returns true, it means that the geofence has been sent to the hardware.
+ * {@link GeofenceHardwareCallback#onGeofenceResume} will be called with the result of the
+ * resume call from the hardware.
+ *
+ * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when
+ * {@link #MONITORING_TYPE_GPS_HARDWARE} is used.
+ *
+ * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
+ * geofencing in hardware.
+ *
+ * <p>This API should not be called directly by the app developers. A higher level api
+ * which abstracts the hardware should be used instead. All the checks are done by the higher
+ * level public API. Any needed locking should be handled by the higher level API.
+ *
+ * @param geofenceId The id of the geofence.
+ * @param monitoringType The type of the hardware subsystem that should be used
+ * to monitor the geofence.
+ * @param monitorTransition Bitwise OR of {@link #GEOFENCE_ENTERED},
+ * {@link #GEOFENCE_EXITED}, {@link #GEOFENCE_UNCERTAIN}
+ * @return true when the geofence is successfully sent to the hardware for resumption.
+ */
+ public boolean resumeGeofence(int geofenceId, int monitoringType, int monitorTransition) {
+ try {
+ return mService.resumeGeofence(geofenceId, monitoringType, monitorTransition);
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Register the callback to be notified when the state of a hardware geofence
+ * monitoring system changes. For instance, it can change from
+ * {@link #MONITOR_CURRENTLY_AVAILABLE} to {@link #MONITOR_CURRENTLY_UNAVAILABLE}
+ *
+ * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when
+ * {@link #MONITORING_TYPE_GPS_HARDWARE} is used.
+ *
+ * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
+ * geofencing in hardware.
+ *
+ * <p>This API should not be called directly by the app developers. A higher level api
+ * which abstracts the hardware should be used instead. All the checks are done by the higher
+ * level public API. Any needed locking should be handled by the higher level API.
+ *
+ * <p> The same callback object can be used to be informed of geofence transitions
+ * and state changes of the underlying hardware subsystem.
+ *
+ * @param monitoringType Type of the monitor
+ * @param callback Callback that will be called.
+ * @return true on success
+ */
+ public boolean registerForMonitorStateChangeCallback(int monitoringType,
+ GeofenceHardwareMonitorCallback callback) {
+ try {
+ return mService.registerForMonitorStateChangeCallback(monitoringType,
+ getMonitorCallbackWrapper(callback));
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
+ * Unregister the callback that was used with {@link #registerForMonitorStateChangeCallback}
+ * to notify when the state of the hardware geofence monitoring system changes.
+ *
+ * <p> Requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission when
+ * {@link #MONITORING_TYPE_GPS_HARDWARE} is used.
+ *
+ * <p> Requires {@link android.Manifest.permission#LOCATION_HARDWARE} permission to access
+ * geofencing in hardware.
+ *
+ * <p>This API should not be called directly by the app developers. A higher level api
+ * which abstracts the hardware should be used instead. All the checks are done by the higher
+ * level public API. Any needed locking should be handled by the higher level API.
+ *
+ * @param monitoringType Type of the monitor
+ * @param callback Callback that will be called.
+ * @return true on success
+ */
+ public boolean unregisterForMonitorStateChangeCallback(int monitoringType,
+ GeofenceHardwareMonitorCallback callback) {
+ boolean result = false;
+ try {
+ result = mService.unregisterForMonitorStateChangeCallback(monitoringType,
+ getMonitorCallbackWrapper(callback));
+ if (result) removeMonitorCallback(callback);
+
+ } catch (RemoteException e) {
+ }
+ return result;
+ }
+
+
+ private void removeCallback(GeofenceHardwareCallback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
+ }
+
+ private GeofenceHardwareCallbackWrapper getCallbackWrapper(GeofenceHardwareCallback callback) {
+ synchronized (mCallbacks) {
+ GeofenceHardwareCallbackWrapper wrapper = mCallbacks.get(callback);
+ if (wrapper == null) {
+ wrapper = new GeofenceHardwareCallbackWrapper(callback);
+ mCallbacks.put(callback, wrapper);
+ }
+ return wrapper;
+ }
+ }
+
+ private void removeMonitorCallback(GeofenceHardwareMonitorCallback callback) {
+ synchronized (mMonitorCallbacks) {
+ mMonitorCallbacks.remove(callback);
+ }
+ }
+
+ private GeofenceHardwareMonitorCallbackWrapper getMonitorCallbackWrapper(
+ GeofenceHardwareMonitorCallback callback) {
+ synchronized (mMonitorCallbacks) {
+ GeofenceHardwareMonitorCallbackWrapper wrapper = mMonitorCallbacks.get(callback);
+ if (wrapper == null) {
+ wrapper = new GeofenceHardwareMonitorCallbackWrapper(callback);
+ mMonitorCallbacks.put(callback, wrapper);
+ }
+ return wrapper;
+ }
+ }
+
+ class GeofenceHardwareMonitorCallbackWrapper extends IGeofenceHardwareMonitorCallback.Stub {
+ private WeakReference<GeofenceHardwareMonitorCallback> mCallback;
+
+ GeofenceHardwareMonitorCallbackWrapper(GeofenceHardwareMonitorCallback c) {
+ mCallback = new WeakReference<GeofenceHardwareMonitorCallback>(c);
+ }
+
+ public void onMonitoringSystemChange(int monitoringType, boolean available,
+ Location location) {
+ GeofenceHardwareMonitorCallback c = mCallback.get();
+ if (c != null) c.onMonitoringSystemChange(monitoringType, available, location);
+ }
+ }
+
+ class GeofenceHardwareCallbackWrapper extends IGeofenceHardwareCallback.Stub {
+ private WeakReference<GeofenceHardwareCallback> mCallback;
+
+ GeofenceHardwareCallbackWrapper(GeofenceHardwareCallback c) {
+ mCallback = new WeakReference<GeofenceHardwareCallback>(c);
+ }
+
+ public void onGeofenceTransition(int geofenceId, int transition, Location location,
+ long timestamp, int monitoringType) {
+ GeofenceHardwareCallback c = mCallback.get();
+ if (c != null) {
+ c.onGeofenceTransition(geofenceId, transition, location, timestamp,
+ monitoringType);
+ }
+ }
+
+ public void onGeofenceAdd(int geofenceId, int status) {
+ GeofenceHardwareCallback c = mCallback.get();
+ if (c != null) c.onGeofenceAdd(geofenceId, status);
+ }
+
+ public void onGeofenceRemove(int geofenceId, int status) {
+ GeofenceHardwareCallback c = mCallback.get();
+ if (c != null) {
+ c.onGeofenceRemove(geofenceId, status);
+ removeCallback(c);
+ }
+ }
+
+ public void onGeofencePause(int geofenceId, int status) {
+ GeofenceHardwareCallback c = mCallback.get();
+ if (c != null) {
+ c.onGeofencePause(geofenceId, status);
+ }
+ }
+
+ public void onGeofenceResume(int geofenceId, int status) {
+ GeofenceHardwareCallback c = mCallback.get();
+ if (c != null) c.onGeofenceResume(geofenceId, status);
+ }
+ }
+}
diff --git a/core/java/android/hardware/location/GeofenceHardwareCallback.java b/core/java/android/hardware/location/GeofenceHardwareCallback.java
new file mode 100644
index 0000000..6cad3da
--- /dev/null
+++ b/core/java/android/hardware/location/GeofenceHardwareCallback.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.location.Location;
+
+/**
+ * The callback class associated with the APIs in {@link GeofenceHardware}
+ */
+public abstract class GeofenceHardwareCallback {
+ /**
+ * The callback called when there is a transition to report for the specific
+ * geofence.
+ *
+ * @param geofenceId The geofence ID of the geofence
+ * @param transition One of {@link GeofenceHardware#GEOFENCE_ENTERED},
+ * {@link GeofenceHardware#GEOFENCE_EXITED}, {@link GeofenceHardware#GEOFENCE_UNCERTAIN}
+ * @param location The last known location according to the monitoring system.
+ * @param timestamp The timestamp (elapsed real time in milliseconds) when the transition was
+ * detected
+ * @param monitoringType Type of the monitoring system.
+ */
+ public void onGeofenceTransition(int geofenceId, int transition, Location location,
+ long timestamp, int monitoringType) {
+ }
+
+ /**
+ * The callback called to notify the success or failure of the add call.
+ *
+ * @param geofenceId The ID of the geofence.
+ * @param status One of {@link GeofenceHardware#GEOFENCE_SUCCESS},
+ * {@link GeofenceHardware#GEOFENCE_ERROR_ID_EXISTS},
+ * {@link GeofenceHardware#GEOFENCE_ERROR_INVALID_TRANSITION},
+ * {@link GeofenceHardware#GEOFENCE_ERROR_TOO_MANY_GEOFENCES},
+ * {@link GeofenceHardware#GEOFENCE_FAILURE}
+ */
+ public void onGeofenceAdd(int geofenceId, int status) {
+ }
+
+ /**
+ * The callback called to notify the success or failure of the remove call.
+ *
+ * @param geofenceId The ID of the geofence.
+ * @param status One of {@link GeofenceHardware#GEOFENCE_SUCCESS},
+ * {@link GeofenceHardware#GEOFENCE_ERROR_ID_UNKNOWN},
+ * {@link GeofenceHardware#GEOFENCE_FAILURE}
+ */
+ public void onGeofenceRemove(int geofenceId, int status) {
+ }
+
+ /**
+ * The callback called to notify the success or failure of the pause call.
+ *
+ * @param geofenceId The ID of the geofence.
+ * @param status One of {@link GeofenceHardware#GEOFENCE_SUCCESS},
+ * {@link GeofenceHardware#GEOFENCE_ERROR_ID_UNKNOWN},
+ * {@link GeofenceHardware#GEOFENCE_FAILURE}
+ */
+ public void onGeofencePause(int geofenceId, int status) {
+ }
+
+ /**
+ * The callback called to notify the success or failure of the resume call.
+ *
+ * @param geofenceId The ID of the geofence.
+ * @param status One of {@link GeofenceHardware#GEOFENCE_SUCCESS},
+ * {@link GeofenceHardware#GEOFENCE_ERROR_ID_UNKNOWN},
+ * {@link GeofenceHardware#GEOFENCE_ERROR_INVALID_TRANSITION},
+ * {@link GeofenceHardware#GEOFENCE_FAILURE}
+ */
+ public void onGeofenceResume(int geofenceId, int status) {
+ }
+}
diff --git a/core/java/android/hardware/location/GeofenceHardwareImpl.java b/core/java/android/hardware/location/GeofenceHardwareImpl.java
new file mode 100644
index 0000000..77e3143
--- /dev/null
+++ b/core/java/android/hardware/location/GeofenceHardwareImpl.java
@@ -0,0 +1,773 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.IGpsGeofenceHardware;
+import android.location.Location;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * This class manages the geofences which are handled by hardware.
+ *
+ * @hide
+ */
+public final class GeofenceHardwareImpl {
+ private static final String TAG = "GeofenceHardwareImpl";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Context mContext;
+ private static GeofenceHardwareImpl sInstance;
+ private PowerManager.WakeLock mWakeLock;
+ private final SparseArray<IGeofenceHardwareCallback> mGeofences =
+ new SparseArray<IGeofenceHardwareCallback>();
+ private final ArrayList<IGeofenceHardwareMonitorCallback>[] mCallbacks =
+ new ArrayList[GeofenceHardware.NUM_MONITORS];
+ private final ArrayList<Reaper> mReapers = new ArrayList<Reaper>();
+
+ private IGpsGeofenceHardware mGpsService;
+
+ private int[] mSupportedMonitorTypes = new int[GeofenceHardware.NUM_MONITORS];
+
+ // mGeofenceHandler message types
+ private static final int GEOFENCE_TRANSITION_CALLBACK = 1;
+ private static final int ADD_GEOFENCE_CALLBACK = 2;
+ private static final int REMOVE_GEOFENCE_CALLBACK = 3;
+ private static final int PAUSE_GEOFENCE_CALLBACK = 4;
+ private static final int RESUME_GEOFENCE_CALLBACK = 5;
+ private static final int GEOFENCE_CALLBACK_BINDER_DIED = 6;
+
+ // mCallbacksHandler message types
+ private static final int GPS_GEOFENCE_STATUS = 1;
+ private static final int CALLBACK_ADD = 2;
+ private static final int CALLBACK_REMOVE = 3;
+ private static final int MONITOR_CALLBACK_BINDER_DIED = 4;
+
+ // mReaperHandler message types
+ private static final int REAPER_GEOFENCE_ADDED = 1;
+ private static final int REAPER_MONITOR_CALLBACK_ADDED = 2;
+ private static final int REAPER_REMOVED = 3;
+
+ // The following constants need to match GpsLocationFlags enum in gps.h
+ private static final int LOCATION_INVALID = 0;
+ private static final int LOCATION_HAS_LAT_LONG = 1;
+ private static final int LOCATION_HAS_ALTITUDE = 2;
+ private static final int LOCATION_HAS_SPEED = 4;
+ private static final int LOCATION_HAS_BEARING = 8;
+ private static final int LOCATION_HAS_ACCURACY = 16;
+
+ // Resolution level constants used for permission checks.
+ // These constants must be in increasing order of finer resolution.
+ private static final int RESOLUTION_LEVEL_NONE = 1;
+ private static final int RESOLUTION_LEVEL_COARSE = 2;
+ private static final int RESOLUTION_LEVEL_FINE = 3;
+
+ // GPS Geofence errors. Should match gps.h constants.
+ private static final int GPS_GEOFENCE_OPERATION_SUCCESS = 0;
+ private static final int GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 100;
+ private static final int GPS_GEOFENCE_ERROR_ID_EXISTS = -101;
+ private static final int GPS_GEOFENCE_ERROR_ID_UNKNOWN = -102;
+ private static final int GPS_GEOFENCE_ERROR_INVALID_TRANSITION = -103;
+ private static final int GPS_GEOFENCE_ERROR_GENERIC = -149;
+
+
+
+ public synchronized static GeofenceHardwareImpl getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new GeofenceHardwareImpl(context);
+ }
+ return sInstance;
+ }
+
+ private GeofenceHardwareImpl(Context context) {
+ mContext = context;
+ // Init everything to unsupported.
+ setMonitorAvailability(GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
+ GeofenceHardware.MONITOR_UNSUPPORTED);
+
+ }
+
+ private void acquireWakeLock() {
+ if (mWakeLock == null) {
+ PowerManager powerManager =
+ (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+ }
+ mWakeLock.acquire();
+ }
+
+ private void releaseWakeLock() {
+ if (mWakeLock.isHeld()) mWakeLock.release();
+ }
+
+ private void updateGpsHardwareAvailability() {
+ //Check which monitors are available.
+ boolean gpsSupported;
+ try {
+ gpsSupported = mGpsService.isHardwareGeofenceSupported();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Remote Exception calling LocationManagerService");
+ gpsSupported = false;
+ }
+
+ if (gpsSupported) {
+ // Its assumed currently available at startup.
+ // native layer will update later.
+ setMonitorAvailability(GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
+ GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE);
+ }
+ }
+
+ public void setGpsHardwareGeofence(IGpsGeofenceHardware service) {
+ if (mGpsService == null) {
+ mGpsService = service;
+ updateGpsHardwareAvailability();
+ } else if (service == null) {
+ mGpsService = null;
+ Log.w(TAG, "GPS Geofence Hardware service seems to have crashed");
+ } else {
+ Log.e(TAG, "Error: GpsService being set again.");
+ }
+ }
+
+ public int[] getMonitoringTypes() {
+ synchronized (mSupportedMonitorTypes) {
+ if (mSupportedMonitorTypes[GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE] !=
+ GeofenceHardware.MONITOR_UNSUPPORTED) {
+ return new int[] {GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE};
+ }
+ return new int[0];
+ }
+ }
+
+ public int getStatusOfMonitoringType(int monitoringType) {
+ synchronized (mSupportedMonitorTypes) {
+ if (monitoringType >= mSupportedMonitorTypes.length || monitoringType < 0) {
+ throw new IllegalArgumentException("Unknown monitoring type");
+ }
+ return mSupportedMonitorTypes[monitoringType];
+ }
+ }
+
+ public boolean addCircularFence(int geofenceId, int monitoringType, double latitude,
+ double longitude, double radius, int lastTransition,int monitorTransitions,
+ int notificationResponsivenes, int unknownTimer, IGeofenceHardwareCallback callback) {
+ // This API is not thread safe. Operations on the same geofence need to be serialized
+ // by upper layers
+ if (DEBUG) {
+ Log.d(TAG, "addCircularFence: GeofenceId: " + geofenceId + " Latitude: " + latitude +
+ " Longitude: " + longitude + " Radius: " + radius + " LastTransition: "
+ + lastTransition + " MonitorTransition: " + monitorTransitions +
+ " NotificationResponsiveness: " + notificationResponsivenes +
+ " UnKnown Timer: " + unknownTimer + " MonitoringType: " + monitoringType);
+
+ }
+ boolean result;
+
+ // The callback must be added before addCircularHardwareGeofence is called otherwise the
+ // callback might not be called after the geofence is added in the geofence hardware.
+ // This also means that the callback must be removed if the addCircularHardwareGeofence
+ // operations is not called or fails.
+ synchronized (mGeofences) {
+ mGeofences.put(geofenceId, callback);
+ }
+
+ switch (monitoringType) {
+ case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE:
+ if (mGpsService == null) return false;
+ try {
+ result = mGpsService.addCircularHardwareGeofence(geofenceId, latitude,
+ longitude, radius, lastTransition, monitorTransitions,
+ notificationResponsivenes, unknownTimer);
+ } catch (RemoteException e) {
+ Log.e(TAG, "AddGeofence: Remote Exception calling LocationManagerService");
+ result = false;
+ }
+ break;
+ default:
+ result = false;
+ }
+ if (result) {
+ Message m = mReaperHandler.obtainMessage(REAPER_GEOFENCE_ADDED, callback);
+ m.arg1 = monitoringType;
+ mReaperHandler.sendMessage(m);
+ } else {
+ synchronized (mGeofences) {
+ mGeofences.remove(geofenceId);
+ }
+ }
+
+ if (DEBUG) Log.d(TAG, "addCircularFence: Result is: " + result);
+ return result;
+ }
+
+ public boolean removeGeofence(int geofenceId, int monitoringType) {
+ // This API is not thread safe. Operations on the same geofence need to be serialized
+ // by upper layers
+ if (DEBUG) Log.d(TAG, "Remove Geofence: GeofenceId: " + geofenceId);
+ boolean result = false;
+
+ synchronized (mGeofences) {
+ if (mGeofences.get(geofenceId) == null) {
+ throw new IllegalArgumentException("Geofence " + geofenceId + " not registered.");
+ }
+ }
+ switch (monitoringType) {
+ case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE:
+ if (mGpsService == null) return false;
+ try {
+ result = mGpsService.removeHardwareGeofence(geofenceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoveGeofence: Remote Exception calling LocationManagerService");
+ result = false;
+ }
+ break;
+ default:
+ result = false;
+ }
+ if (DEBUG) Log.d(TAG, "removeGeofence: Result is: " + result);
+ return result;
+ }
+
+ public boolean pauseGeofence(int geofenceId, int monitoringType) {
+ // This API is not thread safe. Operations on the same geofence need to be serialized
+ // by upper layers
+ if (DEBUG) Log.d(TAG, "Pause Geofence: GeofenceId: " + geofenceId);
+ boolean result;
+ synchronized (mGeofences) {
+ if (mGeofences.get(geofenceId) == null) {
+ throw new IllegalArgumentException("Geofence " + geofenceId + " not registered.");
+ }
+ }
+ switch (monitoringType) {
+ case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE:
+ if (mGpsService == null) return false;
+ try {
+ result = mGpsService.pauseHardwareGeofence(geofenceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "PauseGeofence: Remote Exception calling LocationManagerService");
+ result = false;
+ }
+ break;
+ default:
+ result = false;
+ }
+ if (DEBUG) Log.d(TAG, "pauseGeofence: Result is: " + result);
+ return result;
+ }
+
+
+ public boolean resumeGeofence(int geofenceId, int monitoringType, int monitorTransition) {
+ // This API is not thread safe. Operations on the same geofence need to be serialized
+ // by upper layers
+ if (DEBUG) Log.d(TAG, "Resume Geofence: GeofenceId: " + geofenceId);
+ boolean result;
+ synchronized (mGeofences) {
+ if (mGeofences.get(geofenceId) == null) {
+ throw new IllegalArgumentException("Geofence " + geofenceId + " not registered.");
+ }
+ }
+ switch (monitoringType) {
+ case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE:
+ if (mGpsService == null) return false;
+ try {
+ result = mGpsService.resumeHardwareGeofence(geofenceId, monitorTransition);
+ } catch (RemoteException e) {
+ Log.e(TAG, "ResumeGeofence: Remote Exception calling LocationManagerService");
+ result = false;
+ }
+ break;
+ default:
+ result = false;
+ }
+ if (DEBUG) Log.d(TAG, "resumeGeofence: Result is: " + result);
+ return result;
+ }
+
+ public boolean registerForMonitorStateChangeCallback(int monitoringType,
+ IGeofenceHardwareMonitorCallback callback) {
+ Message reaperMessage =
+ mReaperHandler.obtainMessage(REAPER_MONITOR_CALLBACK_ADDED, callback);
+ reaperMessage.arg1 = monitoringType;
+ mReaperHandler.sendMessage(reaperMessage);
+
+ Message m = mCallbacksHandler.obtainMessage(CALLBACK_ADD, callback);
+ m.arg1 = monitoringType;
+ mCallbacksHandler.sendMessage(m);
+ return true;
+ }
+
+ public boolean unregisterForMonitorStateChangeCallback(int monitoringType,
+ IGeofenceHardwareMonitorCallback callback) {
+ Message m = mCallbacksHandler.obtainMessage(CALLBACK_REMOVE, callback);
+ m.arg1 = monitoringType;
+ mCallbacksHandler.sendMessage(m);
+ return true;
+ }
+
+ private Location getLocation(int flags, double latitude,
+ double longitude, double altitude, float speed, float bearing, float accuracy,
+ long timestamp) {
+ if (DEBUG) Log.d(TAG, "GetLocation: " + flags + ":" + latitude);
+ Location location = new Location(LocationManager.GPS_PROVIDER);
+ if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
+ location.setLatitude(latitude);
+ location.setLongitude(longitude);
+ location.setTime(timestamp);
+ // It would be nice to push the elapsed real-time timestamp
+ // further down the stack, but this is still useful
+ location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+ }
+ if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
+ location.setAltitude(altitude);
+ } else {
+ location.removeAltitude();
+ }
+ if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
+ location.setSpeed(speed);
+ } else {
+ location.removeSpeed();
+ }
+ if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) {
+ location.setBearing(bearing);
+ } else {
+ location.removeBearing();
+ }
+ if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) {
+ location.setAccuracy(accuracy);
+ } else {
+ location.removeAccuracy();
+ }
+ return location;
+ }
+
+ /**
+ * called from GpsLocationProvider to report geofence transition
+ */
+ public void reportGpsGeofenceTransition(int geofenceId, int flags, double latitude,
+ double longitude, double altitude, float speed, float bearing, float accuracy,
+ long timestamp, int transition, long transitionTimestamp) {
+ if (DEBUG) Log.d(TAG, "GeofenceTransition: Flags: " + flags + " Lat: " + latitude +
+ " Long: " + longitude + " Altitude: " + altitude + " Speed: " + speed + " Bearing: " +
+ bearing + " Accuracy: " + accuracy + " Timestamp: " + timestamp + " Transition: " +
+ transition + " TransitionTimestamp: " + transitionTimestamp);
+ Location location = getLocation(flags, latitude, longitude, altitude, speed, bearing,
+ accuracy, timestamp);
+ GeofenceTransition t = new GeofenceTransition(geofenceId, transition, timestamp, location);
+ acquireWakeLock();
+ Message m = mGeofenceHandler.obtainMessage(GEOFENCE_TRANSITION_CALLBACK, t);
+ mGeofenceHandler.sendMessage(m);
+ }
+
+ /**
+ * called from GpsLocationProvider to report GPS status change.
+ */
+ public void reportGpsGeofenceStatus(int status, int flags, double latitude,
+ double longitude, double altitude, float speed, float bearing, float accuracy,
+ long timestamp) {
+ Location location = getLocation(flags, latitude, longitude, altitude, speed, bearing,
+ accuracy, timestamp);
+ boolean available = false;
+ if (status == GeofenceHardware.GPS_GEOFENCE_AVAILABLE) available = true;
+
+ int val = (available ? GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE :
+ GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE);
+ setMonitorAvailability(GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, val);
+
+ acquireWakeLock();
+ Message m = mCallbacksHandler.obtainMessage(GPS_GEOFENCE_STATUS, location);
+ m.arg1 = val;
+ mCallbacksHandler.sendMessage(m);
+ }
+
+ /**
+ * called from GpsLocationProvider add geofence callback.
+ */
+ public void reportGpsGeofenceAddStatus(int geofenceId, int status) {
+ if (DEBUG) Log.d(TAG, "Add Callback: GPS : Id: " + geofenceId + " Status: " + status);
+ acquireWakeLock();
+ Message m = mGeofenceHandler.obtainMessage(ADD_GEOFENCE_CALLBACK);
+ m.arg1 = geofenceId;
+ m.arg2 = getGeofenceStatus(status);
+ mGeofenceHandler.sendMessage(m);
+ }
+
+ /**
+ * called from GpsLocationProvider remove geofence callback.
+ */
+ public void reportGpsGeofenceRemoveStatus(int geofenceId, int status) {
+ if (DEBUG) Log.d(TAG, "Remove Callback: GPS : Id: " + geofenceId + " Status: " + status);
+ acquireWakeLock();
+ Message m = mGeofenceHandler.obtainMessage(REMOVE_GEOFENCE_CALLBACK);
+ m.arg1 = geofenceId;
+ m.arg2 = getGeofenceStatus(status);
+ mGeofenceHandler.sendMessage(m);
+ }
+
+ /**
+ * called from GpsLocationProvider pause geofence callback.
+ */
+ public void reportGpsGeofencePauseStatus(int geofenceId, int status) {
+ if (DEBUG) Log.d(TAG, "Pause Callback: GPS : Id: " + geofenceId + " Status: " + status);
+ acquireWakeLock();
+ Message m = mGeofenceHandler.obtainMessage(PAUSE_GEOFENCE_CALLBACK);
+ m.arg1 = geofenceId;
+ m.arg2 = getGeofenceStatus(status);
+ mGeofenceHandler.sendMessage(m);
+ }
+
+ /**
+ * called from GpsLocationProvider resume geofence callback.
+ */
+ public void reportGpsGeofenceResumeStatus(int geofenceId, int status) {
+ if (DEBUG) Log.d(TAG, "Resume Callback: GPS : Id: " + geofenceId + " Status: " + status);
+ acquireWakeLock();
+ Message m = mGeofenceHandler.obtainMessage(RESUME_GEOFENCE_CALLBACK);
+ m.arg1 = geofenceId;
+ m.arg2 = getGeofenceStatus(status);
+ mGeofenceHandler.sendMessage(m);
+ }
+
+ // All operations on mGeofences
+ private Handler mGeofenceHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ int geofenceId;
+ int status;
+ IGeofenceHardwareCallback callback;
+ switch (msg.what) {
+ case ADD_GEOFENCE_CALLBACK:
+ geofenceId = msg.arg1;
+ synchronized (mGeofences) {
+ callback = mGeofences.get(geofenceId);
+ }
+
+ if (callback != null) {
+ try {
+ callback.onGeofenceAdd(geofenceId, msg.arg2);
+ } catch (RemoteException e) {Log.i(TAG, "Remote Exception:" + e);}
+ }
+ releaseWakeLock();
+ break;
+ case REMOVE_GEOFENCE_CALLBACK:
+ geofenceId = msg.arg1;
+ synchronized (mGeofences) {
+ callback = mGeofences.get(geofenceId);
+ }
+
+ if (callback != null) {
+ try {
+ callback.onGeofenceRemove(geofenceId, msg.arg2);
+ } catch (RemoteException e) {}
+ synchronized (mGeofences) {
+ mGeofences.remove(geofenceId);
+ }
+ }
+ releaseWakeLock();
+ break;
+
+ case PAUSE_GEOFENCE_CALLBACK:
+ geofenceId = msg.arg1;
+ synchronized (mGeofences) {
+ callback = mGeofences.get(geofenceId);
+ }
+
+ if (callback != null) {
+ try {
+ callback.onGeofencePause(geofenceId, msg.arg2);
+ } catch (RemoteException e) {}
+ }
+ releaseWakeLock();
+ break;
+
+ case RESUME_GEOFENCE_CALLBACK:
+ geofenceId = msg.arg1;
+ synchronized (mGeofences) {
+ callback = mGeofences.get(geofenceId);
+ }
+
+ if (callback != null) {
+ try {
+ callback.onGeofenceResume(geofenceId, msg.arg2);
+ } catch (RemoteException e) {}
+ }
+ releaseWakeLock();
+ break;
+
+ case GEOFENCE_TRANSITION_CALLBACK:
+ GeofenceTransition geofenceTransition = (GeofenceTransition)(msg.obj);
+ synchronized (mGeofences) {
+ callback = mGeofences.get(geofenceTransition.mGeofenceId);
+ }
+
+ if (DEBUG) Log.d(TAG, "GeofenceTransistionCallback: GPS : GeofenceId: " +
+ geofenceTransition.mGeofenceId +
+ " Transition: " + geofenceTransition.mTransition +
+ " Location: " + geofenceTransition.mLocation + ":" + mGeofences);
+
+ if (callback != null) {
+ try {
+ callback.onGeofenceTransition(
+ geofenceTransition.mGeofenceId, geofenceTransition.mTransition,
+ geofenceTransition.mLocation, geofenceTransition.mTimestamp,
+ GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE);
+ } catch (RemoteException e) {}
+ }
+ releaseWakeLock();
+ break;
+ case GEOFENCE_CALLBACK_BINDER_DIED:
+ // Find all geofences associated with this callback and remove them.
+ callback = (IGeofenceHardwareCallback) (msg.obj);
+ if (DEBUG) Log.d(TAG, "Geofence callback reaped:" + callback);
+ int monitoringType = msg.arg1;
+ synchronized (mGeofences) {
+ for (int i = 0; i < mGeofences.size(); i++) {
+ if (mGeofences.valueAt(i).equals(callback)) {
+ geofenceId = mGeofences.keyAt(i);
+ removeGeofence(mGeofences.keyAt(i), monitoringType);
+ mGeofences.remove(geofenceId);
+ }
+ }
+ }
+ }
+ }
+ };
+
+ // All operations on mCallbacks
+ private Handler mCallbacksHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ int monitoringType;
+ ArrayList<IGeofenceHardwareMonitorCallback> callbackList;
+ IGeofenceHardwareMonitorCallback callback;
+
+ switch (msg.what) {
+ case GPS_GEOFENCE_STATUS:
+ Location location = (Location) msg.obj;
+ int val = msg.arg1;
+ boolean available;
+ available = (val == GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE ?
+ true : false);
+ callbackList = mCallbacks[GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE];
+ if (callbackList != null) {
+ if (DEBUG) Log.d(TAG, "MonitoringSystemChangeCallback: GPS : " + available);
+
+ for (IGeofenceHardwareMonitorCallback c: callbackList) {
+ try {
+ c.onMonitoringSystemChange(
+ GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, available,
+ location);
+ } catch (RemoteException e) {}
+ }
+ }
+ releaseWakeLock();
+ break;
+ case CALLBACK_ADD:
+ monitoringType = msg.arg1;
+ callback = (IGeofenceHardwareMonitorCallback) msg.obj;
+ callbackList = mCallbacks[monitoringType];
+ if (callbackList == null) {
+ callbackList = new ArrayList<IGeofenceHardwareMonitorCallback>();
+ mCallbacks[monitoringType] = callbackList;
+ }
+ if (!callbackList.contains(callback)) callbackList.add(callback);
+ break;
+ case CALLBACK_REMOVE:
+ monitoringType = msg.arg1;
+ callback = (IGeofenceHardwareMonitorCallback) msg.obj;
+ callbackList = mCallbacks[monitoringType];
+ if (callbackList != null) {
+ callbackList.remove(callback);
+ }
+ break;
+ case MONITOR_CALLBACK_BINDER_DIED:
+ callback = (IGeofenceHardwareMonitorCallback) msg.obj;
+ if (DEBUG) Log.d(TAG, "Monitor callback reaped:" + callback);
+ callbackList = mCallbacks[msg.arg1];
+ if (callbackList != null && callbackList.contains(callback)) {
+ callbackList.remove(callback);
+ }
+ }
+ }
+ };
+
+ // All operations on mReaper
+ private Handler mReaperHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ Reaper r;
+ IGeofenceHardwareCallback callback;
+ IGeofenceHardwareMonitorCallback monitorCallback;
+ int monitoringType;
+
+ switch (msg.what) {
+ case REAPER_GEOFENCE_ADDED:
+ callback = (IGeofenceHardwareCallback) msg.obj;
+ monitoringType = msg.arg1;
+ r = new Reaper(callback, monitoringType);
+ if (!mReapers.contains(r)) {
+ mReapers.add(r);
+ IBinder b = callback.asBinder();
+ try {
+ b.linkToDeath(r, 0);
+ } catch (RemoteException e) {}
+ }
+ break;
+ case REAPER_MONITOR_CALLBACK_ADDED:
+ monitorCallback = (IGeofenceHardwareMonitorCallback) msg.obj;
+ monitoringType = msg.arg1;
+
+ r = new Reaper(monitorCallback, monitoringType);
+ if (!mReapers.contains(r)) {
+ mReapers.add(r);
+ IBinder b = monitorCallback.asBinder();
+ try {
+ b.linkToDeath(r, 0);
+ } catch (RemoteException e) {}
+ }
+ break;
+ case REAPER_REMOVED:
+ r = (Reaper) msg.obj;
+ mReapers.remove(r);
+ }
+ }
+ };
+
+ private class GeofenceTransition {
+ private int mGeofenceId, mTransition;
+ private long mTimestamp;
+ private Location mLocation;
+
+ GeofenceTransition(int geofenceId, int transition, long timestamp, Location location) {
+ mGeofenceId = geofenceId;
+ mTransition = transition;
+ mTimestamp = timestamp;
+ mLocation = location;
+ }
+ }
+
+ private void setMonitorAvailability(int monitor, int val) {
+ synchronized (mSupportedMonitorTypes) {
+ mSupportedMonitorTypes[monitor] = val;
+ }
+ }
+
+
+ int getMonitoringResolutionLevel(int monitoringType) {
+ switch (monitoringType) {
+ case GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE:
+ return RESOLUTION_LEVEL_FINE;
+ }
+ return RESOLUTION_LEVEL_NONE;
+ }
+
+ class Reaper implements IBinder.DeathRecipient {
+ private IGeofenceHardwareMonitorCallback mMonitorCallback;
+ private IGeofenceHardwareCallback mCallback;
+ private int mMonitoringType;
+
+ Reaper(IGeofenceHardwareCallback c, int monitoringType) {
+ mCallback = c;
+ mMonitoringType = monitoringType;
+ }
+
+ Reaper(IGeofenceHardwareMonitorCallback c, int monitoringType) {
+ mMonitorCallback = c;
+ mMonitoringType = monitoringType;
+ }
+
+ @Override
+ public void binderDied() {
+ Message m;
+ if (mCallback != null) {
+ m = mGeofenceHandler.obtainMessage(GEOFENCE_CALLBACK_BINDER_DIED, mCallback);
+ m.arg1 = mMonitoringType;
+ mGeofenceHandler.sendMessage(m);
+ } else if (mMonitorCallback != null) {
+ m = mCallbacksHandler.obtainMessage(MONITOR_CALLBACK_BINDER_DIED, mMonitorCallback);
+ m.arg1 = mMonitoringType;
+ mCallbacksHandler.sendMessage(m);
+ }
+ Message reaperMessage = mReaperHandler.obtainMessage(REAPER_REMOVED, this);
+ mReaperHandler.sendMessage(reaperMessage);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mCallback != null ? mCallback.hashCode() : 0);
+ result = 31 * result + (mMonitorCallback != null ? mMonitorCallback.hashCode() : 0);
+ result = 31 * result + mMonitoringType;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) return false;
+ if (obj == this) return true;
+
+ Reaper rhs = (Reaper) obj;
+ return rhs.mCallback == mCallback && rhs.mMonitorCallback == mMonitorCallback &&
+ rhs.mMonitoringType == mMonitoringType;
+ }
+ }
+
+ int getAllowedResolutionLevel(int pid, int uid) {
+ if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
+ pid, uid) == PackageManager.PERMISSION_GRANTED) {
+ return RESOLUTION_LEVEL_FINE;
+ } else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ pid, uid) == PackageManager.PERMISSION_GRANTED) {
+ return RESOLUTION_LEVEL_COARSE;
+ } else {
+ return RESOLUTION_LEVEL_NONE;
+ }
+ }
+
+ private int getGeofenceStatus(int status) {
+ switch (status) {
+ case GPS_GEOFENCE_OPERATION_SUCCESS:
+ return GeofenceHardware.GEOFENCE_SUCCESS;
+ case GPS_GEOFENCE_ERROR_GENERIC:
+ return GeofenceHardware.GEOFENCE_FAILURE;
+ case GPS_GEOFENCE_ERROR_ID_EXISTS:
+ return GeofenceHardware.GEOFENCE_ERROR_ID_EXISTS;
+ case GPS_GEOFENCE_ERROR_INVALID_TRANSITION:
+ return GeofenceHardware.GEOFENCE_ERROR_INVALID_TRANSITION;
+ case GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES:
+ return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES;
+ case GPS_GEOFENCE_ERROR_ID_UNKNOWN:
+ return GeofenceHardware.GEOFENCE_ERROR_ID_UNKNOWN;
+ }
+ return -1;
+ }
+}
diff --git a/core/java/android/hardware/location/GeofenceHardwareMonitorCallback.java b/core/java/android/hardware/location/GeofenceHardwareMonitorCallback.java
new file mode 100644
index 0000000..b8e927e
--- /dev/null
+++ b/core/java/android/hardware/location/GeofenceHardwareMonitorCallback.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.location.Location;
+
+/**
+ * The callback class associated with the status change of hardware montiors
+ * in {@link GeofenceHardware}
+ */
+public abstract class GeofenceHardwareMonitorCallback {
+ /**
+ * The callback called when the state of a monitoring system changes.
+ * {@link GeofenceHardware#MONITORING_TYPE_GPS_HARDWARE} is an example of a
+ * monitoring system
+ *
+ * @param monitoringType The type of the monitoring system.
+ * @param available Indicates whether the system is currenty available or not.
+ * @param location The last known location according to the monitoring system.
+ */
+ public void onMonitoringSystemChange(int monitoringType, boolean available, Location location) {
+ }
+}
diff --git a/core/java/android/hardware/location/GeofenceHardwareRequest.java b/core/java/android/hardware/location/GeofenceHardwareRequest.java
new file mode 100644
index 0000000..6e7b592
--- /dev/null
+++ b/core/java/android/hardware/location/GeofenceHardwareRequest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.location.Location;
+
+/**
+ * This class represents the characteristics of the geofence.
+ *
+ * <p> Use this in conjunction with {@link GeofenceHardware} APIs.
+ */
+
+public final class GeofenceHardwareRequest {
+ static final int GEOFENCE_TYPE_CIRCLE = 0;
+ private int mType;
+ private double mLatitude;
+ private double mLongitude;
+ private double mRadius;
+ private int mLastTransition = GeofenceHardware.GEOFENCE_UNCERTAIN;
+ private int mUnknownTimer = 30000; // 30 secs
+ private int mMonitorTransitions = GeofenceHardware.GEOFENCE_UNCERTAIN |
+ GeofenceHardware.GEOFENCE_ENTERED | GeofenceHardware.GEOFENCE_EXITED;
+ private int mNotificationResponsiveness = 5000; // 5 secs
+
+ private void setCircularGeofence(double latitude, double longitude, double radius) {
+ mLatitude = latitude;
+ mLongitude = longitude;
+ mRadius = radius;
+ mType = GEOFENCE_TYPE_CIRCLE;
+ }
+
+ /**
+ * Create a circular geofence.
+ *
+ * @param latitude Latitude of the geofence
+ * @param longitude Longitude of the geofence
+ * @param radius Radius of the geofence (in meters)
+ */
+ public static GeofenceHardwareRequest createCircularGeofence(double latitude,
+ double longitude, double radius) {
+ GeofenceHardwareRequest geofenceRequest = new GeofenceHardwareRequest();
+ geofenceRequest.setCircularGeofence(latitude, longitude, radius);
+ return geofenceRequest;
+ }
+
+ /**
+ * Set the last known transition of the geofence.
+ *
+ * @param lastTransition The current state of the geofence. Can be one of
+ * {@link GeofenceHardware#GEOFENCE_ENTERED}, {@link GeofenceHardware#GEOFENCE_EXITED},
+ * {@link GeofenceHardware#GEOFENCE_UNCERTAIN}.
+ */
+ public void setLastTransition(int lastTransition) {
+ mLastTransition = lastTransition;
+ }
+
+ /**
+ * Set the unknown timer for this geofence.
+ *
+ * @param unknownTimer The time limit after which the
+ * {@link GeofenceHardware#GEOFENCE_UNCERTAIN} transition
+ * should be triggered. This paramter is defined in milliseconds.
+ */
+ public void setUnknownTimer(int unknownTimer) {
+ mUnknownTimer = unknownTimer;
+ }
+
+ /**
+ * Set the transitions to be monitored.
+ *
+ * @param monitorTransitions Bitwise OR of {@link GeofenceHardware#GEOFENCE_ENTERED},
+ * {@link GeofenceHardware#GEOFENCE_EXITED}, {@link GeofenceHardware#GEOFENCE_UNCERTAIN}
+ */
+ public void setMonitorTransitions(int monitorTransitions) {
+ mMonitorTransitions = monitorTransitions;
+ }
+
+ /**
+ * Set the notification responsiveness of the geofence.
+ *
+ * @param notificationResponsiveness (milliseconds) Defines the best-effort description
+ * of how soon should the callback be called when the transition
+ * associated with the Geofence is triggered. For instance, if
+ * set to 1000 millseconds with {@link GeofenceHardware#GEOFENCE_ENTERED},
+ * the callback will be called 1000 milliseconds within entering
+ * the geofence.
+ */
+ public void setNotificationResponsiveness(int notificationResponsiveness) {
+ mNotificationResponsiveness = notificationResponsiveness;
+ }
+
+ /**
+ * Returns the latitude of this geofence.
+ */
+ public double getLatitude() {
+ return mLatitude;
+ }
+
+ /**
+ * Returns the longitude of this geofence.
+ */
+ public double getLongitude() {
+ return mLongitude;
+ }
+
+ /**
+ * Returns the radius of this geofence.
+ */
+ public double getRadius() {
+ return mRadius;
+ }
+
+ /**
+ * Returns transitions monitored for this geofence.
+ */
+ public int getMonitorTransitions() {
+ return mMonitorTransitions;
+ }
+
+ /**
+ * Returns the unknownTimer of this geofence.
+ */
+ public int getUnknownTimer() {
+ return mUnknownTimer;
+ }
+
+ /**
+ * Returns the notification responsiveness of this geofence.
+ */
+ public int getNotificationResponsiveness() {
+ return mNotificationResponsiveness;
+ }
+
+ /**
+ * Returns the last transition of this geofence.
+ */
+ public int getLastTransition() {
+ return mLastTransition;
+ }
+
+ int getType() {
+ return mType;
+ }
+}
diff --git a/core/java/android/hardware/location/GeofenceHardwareService.java b/core/java/android/hardware/location/GeofenceHardwareService.java
new file mode 100644
index 0000000..3bc70ee
--- /dev/null
+++ b/core/java/android/hardware/location/GeofenceHardwareService.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.Manifest;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.location.IGpsGeofenceHardware;
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * Service that handles hardware geofencing.
+ *
+ * @hide
+ */
+public class GeofenceHardwareService extends Service {
+ private GeofenceHardwareImpl mGeofenceHardwareImpl;
+ private Context mContext;
+
+ @Override
+ public void onCreate() {
+ mContext = this;
+ mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ return false;
+ }
+
+ @Override
+ public void onDestroy() {
+ mGeofenceHardwareImpl = null;
+ }
+
+
+ private void checkPermission(int pid, int uid, int monitoringType) {
+ if (mGeofenceHardwareImpl.getAllowedResolutionLevel(pid, uid) <
+ mGeofenceHardwareImpl.getMonitoringResolutionLevel(monitoringType)) {
+ throw new SecurityException("Insufficient permissions to access hardware geofence for"
+ + " type: " + monitoringType);
+ }
+ }
+
+ private IBinder mBinder = new IGeofenceHardware.Stub() {
+ public void setGpsGeofenceHardware(IGpsGeofenceHardware service) {
+ mGeofenceHardwareImpl.setGpsHardwareGeofence(service);
+ }
+
+ public int[] getMonitoringTypes() {
+ mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
+ "Location Hardware permission not granted to access hardware geofence");
+
+ return mGeofenceHardwareImpl.getMonitoringTypes();
+ }
+
+ public int getStatusOfMonitoringType(int monitoringType) {
+ mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
+ "Location Hardware permission not granted to access hardware geofence");
+
+ return mGeofenceHardwareImpl.getStatusOfMonitoringType(monitoringType);
+ }
+ public boolean addCircularFence(int id, int monitoringType, double lat, double longitude,
+ double radius, int lastTransition, int monitorTransitions, int
+ notificationResponsiveness, int unknownTimer, IGeofenceHardwareCallback callback) {
+ mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
+ "Location Hardware permission not granted to access hardware geofence");
+ checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
+ return mGeofenceHardwareImpl.addCircularFence(id, monitoringType, lat, longitude,
+ radius, lastTransition, monitorTransitions, notificationResponsiveness,
+ unknownTimer, callback);
+ }
+
+ public boolean removeGeofence(int id, int monitoringType) {
+ mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
+ "Location Hardware permission not granted to access hardware geofence");
+
+ checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
+ return mGeofenceHardwareImpl.removeGeofence(id, monitoringType);
+ }
+
+ public boolean pauseGeofence(int id, int monitoringType) {
+ mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
+ "Location Hardware permission not granted to access hardware geofence");
+
+ checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
+ return mGeofenceHardwareImpl.pauseGeofence(id, monitoringType);
+ }
+
+ public boolean resumeGeofence(int id, int monitoringType, int monitorTransitions) {
+ mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
+ "Location Hardware permission not granted to access hardware geofence");
+
+ checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
+ return mGeofenceHardwareImpl.resumeGeofence(id, monitoringType, monitorTransitions);
+ }
+
+ public boolean registerForMonitorStateChangeCallback(int monitoringType,
+ IGeofenceHardwareMonitorCallback callback) {
+ mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
+ "Location Hardware permission not granted to access hardware geofence");
+
+ checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
+ return mGeofenceHardwareImpl.registerForMonitorStateChangeCallback(monitoringType,
+ callback);
+ }
+
+ public boolean unregisterForMonitorStateChangeCallback(int monitoringType,
+ IGeofenceHardwareMonitorCallback callback) {
+ mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
+ "Location Hardware permission not granted to access hardware geofence");
+
+ checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType);
+ return mGeofenceHardwareImpl.unregisterForMonitorStateChangeCallback(monitoringType,
+ callback);
+ }
+ };
+}
diff --git a/core/java/android/hardware/location/IGeofenceHardware.aidl b/core/java/android/hardware/location/IGeofenceHardware.aidl
new file mode 100644
index 0000000..6900070
--- /dev/null
+++ b/core/java/android/hardware/location/IGeofenceHardware.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENS E-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.location.IGpsGeofenceHardware;
+import android.hardware.location.IGeofenceHardwareCallback;
+import android.hardware.location.IGeofenceHardwareMonitorCallback;
+
+/** @hide */
+interface IGeofenceHardware {
+ void setGpsGeofenceHardware(in IGpsGeofenceHardware service);
+ int[] getMonitoringTypes();
+ int getStatusOfMonitoringType(int monitoringType);
+ boolean addCircularFence(int id, int monitoringType, double lat, double longitude,
+ double radius, int lastTransition, int monitorTransitions,
+ int notificationResponsiveness, int unknownTimer,in IGeofenceHardwareCallback callback);
+ boolean removeGeofence(int id, int monitoringType);
+ boolean pauseGeofence(int id, int monitoringType);
+ boolean resumeGeofence(int id, int monitoringType, int monitorTransitions);
+ boolean registerForMonitorStateChangeCallback(int monitoringType,
+ IGeofenceHardwareMonitorCallback callback);
+ boolean unregisterForMonitorStateChangeCallback(int monitoringType,
+ IGeofenceHardwareMonitorCallback callback);
+}
diff --git a/core/java/android/hardware/location/IGeofenceHardwareCallback.aidl b/core/java/android/hardware/location/IGeofenceHardwareCallback.aidl
new file mode 100644
index 0000000..3a8f430
--- /dev/null
+++ b/core/java/android/hardware/location/IGeofenceHardwareCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.location.Location;
+
+/** @hide */
+oneway interface IGeofenceHardwareCallback {
+ void onGeofenceTransition(int geofenceId, int transition, in Location location,
+ long timestamp, int monitoringType);
+ void onGeofenceAdd(int geofenceId, int status);
+ void onGeofenceRemove(int geofenceId, int status);
+ void onGeofencePause(int geofenceId, int status);
+ void onGeofenceResume(int geofenceId, int status);
+}
diff --git a/core/java/com/android/internal/view/IInputConnectionCallback.aidl b/core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl
index 5b5b3df..0b6e04b 100644
--- a/core/java/com/android/internal/view/IInputConnectionCallback.aidl
+++ b/core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,18 +14,11 @@
* limitations under the License.
*/
-package com.android.internal.view;
+package android.hardware.location;
-import android.graphics.Rect;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.inputmethod.TextBoxAttribute;
-import com.android.internal.view.IInputContext;
-import android.os.IBinder;
+import android.location.Location;
-/**
- * {@hide}
- */
-oneway interface IInputMethodCallback {
- void finishedEvent(int seq, boolean handled);
+/** @hide */
+oneway interface IGeofenceHardwareMonitorCallback {
+ void onMonitoringSystemChange(int monitoringType, boolean available, in Location location);
}
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 8286686..9bc967f 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -95,4 +95,7 @@ interface IUsbManager
/* Deny USB debugging from the attached host */
void denyUsbDebugging();
+
+ /* Clear public keys installed for secure USB debugging */
+ void clearUsbDebuggingKeys();
}
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index b536490..b2034b2 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -17,7 +17,6 @@
package android.hardware.usb;
import android.os.ParcelFileDescriptor;
-import android.util.Log;
import java.io.FileDescriptor;
@@ -108,6 +107,11 @@ public class UsbDeviceConnection {
* {@link UsbConstants#USB_DIR_OUT}, then the transfer is a write,
* and if it is {@link UsbConstants#USB_DIR_IN}, then the transfer
* is a read.
+ * <p>
+ * This method transfers data starting from index 0 in the buffer.
+ * To specify a different offset, use
+ * {@link #controlTransfer(int, int, int, int, byte[], int, int, int)}.
+ * </p>
*
* @param requestType request type for this transaction
* @param request request ID for this transaction
@@ -122,22 +126,73 @@ public class UsbDeviceConnection {
*/
public int controlTransfer(int requestType, int request, int value,
int index, byte[] buffer, int length, int timeout) {
- return native_control_request(requestType, request, value, index, buffer, length, timeout);
+ return controlTransfer(requestType, request, value, index, buffer, 0, length, timeout);
+ }
+
+ /**
+ * Performs a control transaction on endpoint zero for this device.
+ * The direction of the transfer is determined by the request type.
+ * If requestType & {@link UsbConstants#USB_ENDPOINT_DIR_MASK} is
+ * {@link UsbConstants#USB_DIR_OUT}, then the transfer is a write,
+ * and if it is {@link UsbConstants#USB_DIR_IN}, then the transfer
+ * is a read.
+ *
+ * @param requestType request type for this transaction
+ * @param request request ID for this transaction
+ * @param value value field for this transaction
+ * @param index index field for this transaction
+ * @param buffer buffer for data portion of transaction,
+ * or null if no data needs to be sent or received
+ * @param offset the index of the first byte in the buffer to send or receive
+ * @param length the length of the data to send or receive
+ * @param timeout in milliseconds
+ * @return length of data transferred (or zero) for success,
+ * or negative value for failure
+ */
+ public int controlTransfer(int requestType, int request, int value, int index,
+ byte[] buffer, int offset, int length, int timeout) {
+ checkBounds(buffer, offset, length);
+ return native_control_request(requestType, request, value, index,
+ buffer, offset, length, timeout);
}
/**
* Performs a bulk transaction on the given endpoint.
- * The direction of the transfer is determined by the direction of the endpoint
+ * The direction of the transfer is determined by the direction of the endpoint.
+ * <p>
+ * This method transfers data starting from index 0 in the buffer.
+ * To specify a different offset, use
+ * {@link #bulkTransfer(UsbEndpoint, byte[], int, int, int)}.
+ * </p>
*
* @param endpoint the endpoint for this transaction
- * @param buffer buffer for data to send or receive,
+ * @param buffer buffer for data to send or receive
* @param length the length of the data to send or receive
* @param timeout in milliseconds
* @return length of data transferred (or zero) for success,
* or negative value for failure
*/
- public int bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout) {
- return native_bulk_request(endpoint.getAddress(), buffer, length, timeout);
+ public int bulkTransfer(UsbEndpoint endpoint,
+ byte[] buffer, int length, int timeout) {
+ return bulkTransfer(endpoint, buffer, 0, length, timeout);
+ }
+
+ /**
+ * Performs a bulk transaction on the given endpoint.
+ * The direction of the transfer is determined by the direction of the endpoint.
+ *
+ * @param endpoint the endpoint for this transaction
+ * @param buffer buffer for data to send or receive
+ * @param offset the index of the first byte in the buffer to send or receive
+ * @param length the length of the data to send or receive
+ * @param timeout in milliseconds
+ * @return length of data transferred (or zero) for success,
+ * or negative value for failure
+ */
+ public int bulkTransfer(UsbEndpoint endpoint,
+ byte[] buffer, int offset, int length, int timeout) {
+ checkBounds(buffer, offset, length);
+ return native_bulk_request(endpoint.getAddress(), buffer, offset, length, timeout);
}
/**
@@ -168,6 +223,13 @@ public class UsbDeviceConnection {
return native_get_serial();
}
+ private static void checkBounds(byte[] buffer, int start, int length) {
+ final int bufferLength = (buffer != null ? buffer.length : 0);
+ if (start < 0 || start + length > bufferLength) {
+ throw new IllegalArgumentException("Buffer start or length out of bounds.");
+ }
+ }
+
private native boolean native_open(String deviceName, FileDescriptor pfd);
private native void native_close();
private native int native_get_fd();
@@ -175,8 +237,9 @@ public class UsbDeviceConnection {
private native boolean native_claim_interface(int interfaceID, boolean force);
private native boolean native_release_interface(int interfaceID);
private native int native_control_request(int requestType, int request, int value,
- int index, byte[] buffer, int length, int timeout);
- private native int native_bulk_request(int endpoint, byte[] buffer, int length, int timeout);
+ int index, byte[] buffer, int offset, int length, int timeout);
+ private native int native_bulk_request(int endpoint, byte[] buffer,
+ int offset, int length, int timeout);
private native UsbRequest native_request_wait();
private native String native_get_serial();
}
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index 3c3182a..3531926 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -126,11 +126,12 @@ public abstract class AbstractInputMethodService extends Service
mRevoked = true;
mEnabled = false;
}
-
+
/**
* Take care of dispatching incoming key events to the appropriate
* callbacks on the service, and tell the client when this is done.
*/
+ @Override
public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback) {
boolean handled = event.dispatch(AbstractInputMethodService.this,
mDispatcherState, this);
@@ -143,6 +144,7 @@ public abstract class AbstractInputMethodService extends Service
* Take care of dispatching incoming trackball events to the appropriate
* callbacks on the service, and tell the client when this is done.
*/
+ @Override
public void dispatchTrackballEvent(int seq, MotionEvent event, EventCallback callback) {
boolean handled = onTrackballEvent(event);
if (callback != null) {
@@ -154,6 +156,7 @@ public abstract class AbstractInputMethodService extends Service
* Take care of dispatching incoming generic motion events to the appropriate
* callbacks on the service, and tell the client when this is done.
*/
+ @Override
public void dispatchGenericMotionEvent(int seq, MotionEvent event, EventCallback callback) {
boolean handled = onGenericMotionEvent(event);
if (callback != null) {
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index d78262b..726dcec 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -18,15 +18,20 @@ 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;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.CompletionInfo;
@@ -36,14 +41,10 @@ import android.view.inputmethod.InputMethodSession;
class IInputMethodSessionWrapper extends IInputMethodSession.Stub
implements HandlerCaller.Callback {
private static final String TAG = "InputMethodWrapper";
- private static final boolean DEBUG = false;
private static final int DO_FINISH_INPUT = 60;
private static final int DO_DISPLAY_COMPLETIONS = 65;
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;
@@ -53,34 +54,30 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
HandlerCaller mCaller;
InputMethodSession mInputMethodSession;
-
- // NOTE: we should have a cache of these.
- static class InputMethodEventCallbackWrapper implements InputMethodSession.EventCallback {
- final IInputMethodCallback mCb;
- InputMethodEventCallbackWrapper(IInputMethodCallback cb) {
- mCb = cb;
- }
- public void finishedEvent(int seq, boolean handled) {
- try {
- mCb.finishedEvent(seq, handled);
- } catch (RemoteException e) {
- }
- }
- }
-
+ InputChannel mChannel;
+ ImeInputEventReceiver mReceiver;
+
public IInputMethodSessionWrapper(Context context,
- InputMethodSession inputMethodSession) {
+ InputMethodSession inputMethodSession, InputChannel channel) {
mCaller = new HandlerCaller(context, null,
this, true /*asyncHandler*/);
mInputMethodSession = inputMethodSession;
+ mChannel = channel;
+ if (channel != null) {
+ mReceiver = new ImeInputEventReceiver(channel, context.getMainLooper());
+ }
}
public InputMethodSession getInternalInputMethodSession() {
return mInputMethodSession;
}
+ @Override
public void executeMessage(Message msg) {
- if (mInputMethodSession == null) return;
+ if (mInputMethodSession == null) {
+ // The session has been finished.
+ return;
+ }
switch (msg.what) {
case DO_FINISH_INPUT:
@@ -93,33 +90,6 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
mInputMethodSession.updateExtractedText(msg.arg1,
(ExtractedText)msg.obj);
return;
- case DO_DISPATCH_KEY_EVENT: {
- SomeArgs args = (SomeArgs)msg.obj;
- mInputMethodSession.dispatchKeyEvent(msg.arg1,
- (KeyEvent)args.arg1,
- new InputMethodEventCallbackWrapper(
- (IInputMethodCallback)args.arg2));
- args.recycle();
- return;
- }
- case DO_DISPATCH_TRACKBALL_EVENT: {
- SomeArgs args = (SomeArgs)msg.obj;
- mInputMethodSession.dispatchTrackballEvent(msg.arg1,
- (MotionEvent)args.arg1,
- new InputMethodEventCallbackWrapper(
- (IInputMethodCallback)args.arg2));
- 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: {
SomeArgs args = (SomeArgs)msg.obj;
mInputMethodSession.updateSelection(args.argi1, args.argi2,
@@ -143,7 +113,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
return;
}
case DO_FINISH_SESSION: {
- mInputMethodSession = null;
+ doFinishSession();
return;
}
case DO_VIEW_CLICKED: {
@@ -153,37 +123,37 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
-
+
+ private void doFinishSession() {
+ mInputMethodSession = null;
+ if (mReceiver != null) {
+ mReceiver.dispose();
+ mReceiver = null;
+ }
+ if (mChannel != null) {
+ mChannel.dispose();
+ mChannel = null;
+ }
+ }
+
+ @Override
public void finishInput() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_INPUT));
}
+ @Override
public void displayCompletions(CompletionInfo[] completions) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(
DO_DISPLAY_COMPLETIONS, completions));
}
-
+
+ @Override
public void updateExtractedText(int token, ExtractedText text) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIO(
DO_UPDATE_EXTRACTED_TEXT, token, text));
}
-
- public void dispatchKeyEvent(int seq, KeyEvent event, IInputMethodCallback callback) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_DISPATCH_KEY_EVENT, seq,
- event, callback));
- }
-
- public void dispatchTrackballEvent(int seq, MotionEvent event, IInputMethodCallback callback) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_DISPATCH_TRACKBALL_EVENT, seq,
- event, callback));
- }
-
- public void dispatchGenericMotionEvent(int seq, MotionEvent event,
- IInputMethodCallback callback) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_DISPATCH_GENERIC_MOTION_EVENT, seq,
- event, callback));
- }
+ @Override
public void updateSelection(int oldSelStart, int oldSelEnd,
int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIIIIII(DO_UPDATE_SELECTION,
@@ -191,24 +161,74 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
candidatesStart, candidatesEnd));
}
+ @Override
public void viewClicked(boolean focusChanged) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageI(DO_VIEW_CLICKED, focusChanged ? 1 : 0));
}
+ @Override
public void updateCursor(Rect newCursor) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_UPDATE_CURSOR,
- newCursor));
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_UPDATE_CURSOR, newCursor));
}
-
+
+ @Override
public void appPrivateCommand(String action, Bundle data) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOO(DO_APP_PRIVATE_COMMAND, action, data));
}
-
+
+ @Override
public void toggleSoftInput(int showFlags, int hideFlags) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags));
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags));
}
+ @Override
public void finishSession() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_SESSION));
}
+
+ private final class ImeInputEventReceiver extends InputEventReceiver
+ implements InputMethodSession.EventCallback {
+ private final SparseArray<InputEvent> mPendingEvents = new SparseArray<InputEvent>();
+
+ public ImeInputEventReceiver(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ if (mInputMethodSession == null) {
+ // The session has been finished.
+ finishInputEvent(event, false);
+ return;
+ }
+
+ final int seq = event.getSequenceNumber();
+ mPendingEvents.put(seq, event);
+ if (event instanceof KeyEvent) {
+ KeyEvent keyEvent = (KeyEvent)event;
+ mInputMethodSession.dispatchKeyEvent(seq, keyEvent, this);
+ } else {
+ MotionEvent motionEvent = (MotionEvent)event;
+ if (motionEvent.isFromSource(InputDevice.SOURCE_CLASS_TRACKBALL)) {
+ mInputMethodSession.dispatchTrackballEvent(seq, motionEvent, this);
+ } else {
+ mInputMethodSession.dispatchGenericMotionEvent(seq, motionEvent, this);
+ }
+ }
+ }
+
+ @Override
+ public void finishedEvent(int seq, boolean handled) {
+ int index = mPendingEvents.indexOfKey(seq);
+ if (index >= 0) {
+ InputEvent event = mPendingEvents.valueAt(index);
+ mPendingEvents.removeAt(index);
+ finishInputEvent(event, handled);
+ }
+ }
+ }
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 2d67875..9306373 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -20,8 +20,8 @@ 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;
import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.IInputSessionCallback;
import com.android.internal.view.InputConnectionWrapper;
import android.content.Context;
@@ -32,6 +32,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
+import android.view.InputChannel;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
@@ -53,8 +54,7 @@ import java.util.concurrent.TimeUnit;
class IInputMethodWrapper extends IInputMethod.Stub
implements HandlerCaller.Callback {
private static final String TAG = "InputMethodWrapper";
- private static final boolean DEBUG = false;
-
+
private static final int DO_DUMP = 1;
private static final int DO_ATTACH_TOKEN = 10;
private static final int DO_SET_INPUT_CONTEXT = 20;
@@ -78,20 +78,29 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
// NOTE: we should have a cache of these.
- static class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
+ static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
final Context mContext;
- final IInputMethodCallback mCb;
- InputMethodSessionCallbackWrapper(Context context, IInputMethodCallback cb) {
+ final InputChannel mChannel;
+ final IInputSessionCallback mCb;
+
+ InputMethodSessionCallbackWrapper(Context context, InputChannel channel,
+ IInputSessionCallback cb) {
mContext = context;
+ mChannel = channel;
mCb = cb;
}
+
+ @Override
public void sessionCreated(InputMethodSession session) {
try {
if (session != null) {
IInputMethodSessionWrapper wrap =
- new IInputMethodSessionWrapper(mContext, session);
+ new IInputMethodSessionWrapper(mContext, session, mChannel);
mCb.sessionCreated(wrap);
} else {
+ if (mChannel != null) {
+ mChannel.dispose();
+ }
mCb.sessionCreated(null);
}
} catch (RemoteException e) {
@@ -112,6 +121,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
return mInputMethod.get();
}
+ @Override
public void executeMessage(Message msg) {
InputMethod inputMethod = mInputMethod.get();
// Need a valid reference to the inputMethod for everything except a dump.
@@ -174,8 +184,11 @@ class IInputMethodWrapper extends IInputMethod.Stub
return;
}
case DO_CREATE_SESSION: {
+ SomeArgs args = (SomeArgs)msg.obj;
inputMethod.createSession(new InputMethodSessionCallbackWrapper(
- mCaller.mContext, (IInputMethodCallback)msg.obj));
+ mCaller.mContext, (InputChannel)args.arg1,
+ (IInputSessionCallback)args.arg2));
+ args.recycle();
return;
}
case DO_SET_SESSION_ENABLED:
@@ -197,8 +210,9 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
-
- @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
AbstractInputMethodService target = mTarget.get();
if (target == null) {
return;
@@ -224,10 +238,12 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
}
+ @Override
public void attachToken(IBinder token) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
}
-
+
+ @Override
public void bindInput(InputBinding binding) {
InputConnection ic = new InputConnectionWrapper(
IInputContext.Stub.asInterface(binding.getConnectionToken()));
@@ -235,24 +251,30 @@ class IInputMethodWrapper extends IInputMethod.Stub
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
}
+ @Override
public void unbindInput() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_UNSET_INPUT_CONTEXT));
}
+ @Override
public void startInput(IInputContext inputContext, EditorInfo attribute) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT,
inputContext, attribute));
}
+ @Override
public void restartInput(IInputContext inputContext, EditorInfo attribute) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_RESTART_INPUT,
inputContext, attribute));
}
- public void createSession(IInputMethodCallback callback) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CREATE_SESSION, callback));
+ @Override
+ public void createSession(InputChannel channel, IInputSessionCallback callback) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_SESSION,
+ channel, callback));
}
+ @Override
public void setSessionEnabled(IInputMethodSession session, boolean enabled) {
try {
InputMethodSession ls = ((IInputMethodSessionWrapper)
@@ -263,7 +285,8 @@ class IInputMethodWrapper extends IInputMethod.Stub
Log.w(TAG, "Incoming session not of correct type: " + session, e);
}
}
-
+
+ @Override
public void revokeSession(IInputMethodSession session) {
try {
InputMethodSession ls = ((IInputMethodSessionWrapper)
@@ -273,17 +296,20 @@ class IInputMethodWrapper extends IInputMethod.Stub
Log.w(TAG, "Incoming session not of correct type: " + session, e);
}
}
-
+
+ @Override
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT,
flags, resultReceiver));
}
-
+
+ @Override
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_HIDE_SOFT_INPUT,
flags, resultReceiver));
}
+ @Override
public void changeInputMethodSubtype(InputMethodSubtype subtype) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE,
subtype));
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 6f1cc94..2b15afd 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -48,6 +48,7 @@ import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
+import android.view.WindowManager.BadTokenException;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
@@ -421,7 +422,13 @@ public class InputMethodService extends AbstractInputMethodService {
boolean wasVis = isInputViewShown();
mShowInputFlags = 0;
if (onShowInputRequested(flags, false)) {
- showWindow(true);
+ try {
+ showWindow(true);
+ } catch (BadTokenException e) {
+ if (DEBUG) Log.v(TAG, "BadTokenException: IME is done.");
+ mWindowVisible = false;
+ mWindowAdded = false;
+ }
}
// If user uses hard keyboard, IME button should always be shown.
boolean showing = onEvaluateInputViewShown();
@@ -1955,7 +1962,7 @@ public class InputMethodService extends AbstractInputMethodService {
ic.sendKeyEvent(new KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
- ic.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ ic.sendKeyEvent(new KeyEvent(eventTime, SystemClock.uptimeMillis(),
KeyEvent.ACTION_UP, keyEventCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
}
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
index 4b60f07..e87f84c 100644
--- a/core/java/android/net/BaseNetworkStateTracker.java
+++ b/core/java/android/net/BaseNetworkStateTracker.java
@@ -18,6 +18,7 @@ package android.net;
import android.content.Context;
import android.os.Handler;
+import android.os.Messenger;
import com.android.internal.util.Preconditions;
@@ -101,6 +102,11 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker {
}
@Override
+ public void captivePortalCheckCompleted(boolean isCaptivePortal) {
+ // not implemented
+ }
+
+ @Override
public boolean setRadio(boolean turnOn) {
// Base tracker doesn't handle radios
return true;
@@ -155,4 +161,19 @@ public abstract class BaseNetworkStateTracker implements NetworkStateTracker {
public void setDependencyMet(boolean met) {
// Base tracker doesn't handle dependencies
}
+
+ @Override
+ public void addStackedLink(LinkProperties link) {
+ mLinkProperties.addStackedLink(link);
+ }
+
+ @Override
+ public void removeStackedLink(LinkProperties link) {
+ mLinkProperties.removeStackedLink(link);
+ }
+
+ @Override
+ public void supplyMessenger(Messenger messenger) {
+ // not supported on this network
+ }
}
diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java
index ce71e6b..19c5f39 100644
--- a/core/java/android/net/CaptivePortalTracker.java
+++ b/core/java/android/net/CaptivePortalTracker.java
@@ -25,14 +25,16 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
+import android.os.Handler;
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 android.text.TextUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -41,6 +43,7 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Inet4Address;
+import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
@@ -51,17 +54,15 @@ import com.android.internal.R;
* @hide
*/
public class CaptivePortalTracker extends StateMachine {
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private static final String TAG = "CaptivePortalTracker";
private static final String DEFAULT_SERVER = "clients3.google.com";
- private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
private static final int SOCKET_TIMEOUT_MS = 10000;
private String mServer;
private String mUrl;
- private boolean mNotificationShown = false;
private boolean mIsCaptivePortalCheckEnabled = false;
private IConnectivityManager mConnService;
private TelephonyManager mTelephonyManager;
@@ -81,15 +82,21 @@ public class CaptivePortalTracker extends StateMachine {
private State mActiveNetworkState = new ActiveNetworkState();
private State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState();
+ private static final String SETUP_WIZARD_PACKAGE = "com.google.android.setupwizard";
+ private boolean mDeviceProvisioned = false;
+ private ProvisioningObserver mProvisioningObserver;
+
private CaptivePortalTracker(Context context, IConnectivityManager cs) {
super(TAG);
mContext = context;
mConnService = cs;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ mProvisioningObserver = new ProvisioningObserver();
IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE);
mContext.registerReceiver(mReceiver, filter);
mServer = Settings.Global.getString(mContext.getContentResolver(),
@@ -106,11 +113,31 @@ public class CaptivePortalTracker extends StateMachine {
setInitialState(mNoActiveNetworkState);
}
+ private class ProvisioningObserver extends ContentObserver {
+ ProvisioningObserver() {
+ super(new Handler());
+ mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.DEVICE_PROVISIONED), false, this);
+ onChange(false); // load initial value
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mDeviceProvisioned = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+ }
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ // Normally, we respond to CONNECTIVITY_ACTION, allowing time for the change in
+ // connectivity to stabilize, but if the device is not yet provisioned, respond
+ // immediately to speed up transit through the setup wizard.
+ if ((mDeviceProvisioned && action.equals(ConnectivityManager.CONNECTIVITY_ACTION))
+ || (!mDeviceProvisioned
+ && action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE))) {
NetworkInfo info = intent.getParcelableExtra(
ConnectivityManager.EXTRA_NETWORK_INFO);
sendMessage(obtainMessage(CMD_CONNECTIVITY_CHANGE, info));
@@ -132,12 +159,12 @@ public class CaptivePortalTracker extends StateMachine {
private class DefaultState extends State {
@Override
public void enter() {
- if (DBG) log(getName() + "\n");
+ setNotificationOff();
}
@Override
public boolean processMessage(Message message) {
- if (DBG) log(getName() + message.toString() + "\n");
+ if (DBG) log(getName() + message.toString());
switch (message.what) {
case CMD_DETECT_PORTAL:
NetworkInfo info = (NetworkInfo) message.obj;
@@ -159,23 +186,24 @@ public class CaptivePortalTracker extends StateMachine {
private class NoActiveNetworkState extends State {
@Override
public void enter() {
- if (DBG) log(getName() + "\n");
mNetworkInfo = null;
- /* Clear any previous notification */
- setNotificationVisible(false);
}
@Override
public boolean processMessage(Message message) {
- if (DBG) log(getName() + message.toString() + "\n");
+ if (DBG) log(getName() + message.toString());
InetAddress server;
NetworkInfo info;
switch (message.what) {
case CMD_CONNECTIVITY_CHANGE:
info = (NetworkInfo) message.obj;
- if (info.isConnected() && isActiveNetwork(info)) {
- mNetworkInfo = info;
- transitionTo(mDelayedCaptiveCheckState);
+ if (info.getType() == ConnectivityManager.TYPE_WIFI) {
+ if (info.isConnected() && isActiveNetwork(info)) {
+ mNetworkInfo = info;
+ transitionTo(mDelayedCaptiveCheckState);
+ }
+ } else {
+ log(getName() + " not a wifi connectivity change, ignore");
}
break;
default:
@@ -188,7 +216,7 @@ public class CaptivePortalTracker extends StateMachine {
private class ActiveNetworkState extends State {
@Override
public void enter() {
- if (DBG) log(getName() + "\n");
+ setNotificationOff();
}
@Override
@@ -221,25 +249,47 @@ public class CaptivePortalTracker extends StateMachine {
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);
+ Message message = obtainMessage(CMD_DELAYED_CAPTIVE_CHECK, ++mDelayedCheckToken, 0);
+ if (mDeviceProvisioned) {
+ sendMessageDelayed(message, DELAYED_CHECK_INTERVAL_MS);
+ } else {
+ sendMessage(message);
+ }
}
@Override
public boolean processMessage(Message message) {
- if (DBG) log(getName() + message.toString() + "\n");
+ if (DBG) log(getName() + message.toString());
switch (message.what) {
case CMD_DELAYED_CAPTIVE_CHECK:
if (message.arg1 == mDelayedCheckToken) {
InetAddress server = lookupHost(mServer);
- if (server != null) {
- if (isCaptivePortal(server)) {
- if (DBG) log("Captive network " + mNetworkInfo);
- setNotificationVisible(true);
+ boolean captive = server != null && isCaptivePortal(server);
+ if (captive) {
+ if (DBG) log("Captive network " + mNetworkInfo);
+ } else {
+ if (DBG) log("Not captive network " + mNetworkInfo);
+ }
+ notifyPortalCheckCompleted(mNetworkInfo, captive);
+ if (mDeviceProvisioned) {
+ if (captive) {
+ // Setup Wizard will assist the user in connecting to a captive
+ // portal, so make the notification visible unless during setup
+ try {
+ mConnService.setProvisioningNotificationVisible(true,
+ mNetworkInfo.getType(), mNetworkInfo.getExtraInfo(), mUrl);
+ } catch(RemoteException e) {
+ e.printStackTrace();
+ }
}
+ } else {
+ Intent intent = new Intent(
+ ConnectivityManager.ACTION_CAPTIVE_PORTAL_TEST_COMPLETED);
+ intent.putExtra(ConnectivityManager.EXTRA_IS_CAPTIVE_PORTAL, captive);
+ intent.setPackage(SETUP_WIZARD_PACKAGE);
+ mContext.sendBroadcast(intent);
}
- if (DBG) log("Not captive network " + mNetworkInfo);
+
transitionTo(mActiveNetworkState);
}
break;
@@ -256,12 +306,26 @@ public class CaptivePortalTracker extends StateMachine {
return;
}
try {
+ if (DBG) log("notifyPortalCheckComplete: ni=" + info);
mConnService.captivePortalCheckComplete(info);
} catch(RemoteException e) {
e.printStackTrace();
}
}
+ private void notifyPortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
+ if (info == null) {
+ loge("notifyPortalCheckComplete on null");
+ return;
+ }
+ try {
+ if (DBG) log("notifyPortalCheckCompleted: captive=" + isCaptivePortal + " ni=" + info);
+ mConnService.captivePortalCheckCompleted(info, isCaptivePortal);
+ } catch(RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
private boolean isActiveNetwork(NetworkInfo info) {
try {
NetworkInfo active = mConnService.getActiveNetworkInfo();
@@ -274,6 +338,15 @@ public class CaptivePortalTracker extends StateMachine {
return false;
}
+ private void setNotificationOff() {
+ try {
+ mConnService.setProvisioningNotificationVisible(false, ConnectivityManager.TYPE_NONE,
+ null, null);
+ } catch (RemoteException e) {
+ log("setNotificationOff: " + e);
+ }
+ }
+
/**
* Do a URL fetch on a known server to see if we get the data we expect
*/
@@ -316,67 +389,4 @@ public class CaptivePortalTracker extends StateMachine {
}
return null;
}
-
- private void setNotificationVisible(boolean visible) {
- // if it should be hidden and it is already hidden, then noop
- if (!visible && !mNotificationShown) {
- return;
- }
-
- Resources r = Resources.getSystem();
- NotificationManager notificationManager = (NotificationManager) mContext
- .getSystemService(Context.NOTIFICATION_SERVICE);
-
- if (visible) {
- CharSequence title;
- CharSequence details;
- int icon;
- switch (mNetworkInfo.getType()) {
- case ConnectivityManager.TYPE_WIFI:
- title = r.getString(R.string.wifi_available_sign_in, 0);
- details = r.getString(R.string.network_available_sign_in_detailed,
- mNetworkInfo.getExtraInfo());
- icon = R.drawable.stat_notify_wifi_in_range;
- break;
- case ConnectivityManager.TYPE_MOBILE:
- title = r.getString(R.string.network_available_sign_in, 0);
- // TODO: Change this to pull from NetworkInfo once a printable
- // name has been added to it
- details = mTelephonyManager.getNetworkOperatorName();
- icon = R.drawable.stat_notify_rssi_in_range;
- break;
- default:
- title = r.getString(R.string.network_available_sign_in, 0);
- details = r.getString(R.string.network_available_sign_in_detailed,
- mNetworkInfo.getExtraInfo());
- icon = R.drawable.stat_notify_rssi_in_range;
- break;
- }
-
- Notification notification = new Notification();
- notification.when = 0;
- notification.icon = icon;
- notification.flags = Notification.FLAG_AUTO_CANCEL;
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mUrl));
- intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
- Intent.FLAG_ACTIVITY_NEW_TASK);
- notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
- notification.tickerText = title;
- notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
-
- notificationManager.notify(NOTIFICATION_ID, 1, notification);
- } else {
- notificationManager.cancel(NOTIFICATION_ID, 1);
- }
- 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 6ff1a33..02a6494 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -23,7 +23,9 @@ import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
+import android.os.Messenger;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.provider.Settings;
import java.net.InetAddress;
@@ -62,7 +64,7 @@ public class ConnectivityManager {
* NetworkInfo for the new network is also passed as an extra. This lets
* any receivers of the broadcast know that they should not necessarily
* tell the user that no data traffic will be possible. Instead, the
- * reciever should expect another broadcast soon, indicating either that
+ * receiver should expect another broadcast soon, indicating either that
* the failover attempt succeeded (and so there is still overall data
* connectivity), or that the failover attempt failed, meaning that all
* connectivity has been lost.
@@ -70,6 +72,7 @@ public class ConnectivityManager {
* For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY
* is set to {@code true} if there are no connected networks at all.
*/
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
/**
@@ -78,6 +81,7 @@ public class ConnectivityManager {
*
* @hide
*/
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String CONNECTIVITY_ACTION_IMMEDIATE =
"android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE";
@@ -149,8 +153,8 @@ public class ConnectivityManager {
/**
* 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.
+ * The network becomes active when data transmission is started, or
+ * idle if there is no data transmission for a period of time.
* {@hide}
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -198,91 +202,119 @@ public class ConnectivityManager {
* the network and it's condition.
* @hide
*/
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String INET_CONDITION_ACTION =
"android.net.conn.INET_CONDITION_ACTION";
/**
- * Broadcast Action: A tetherable connection has come or gone
- * TODO - finish the doc
+ * Broadcast Action: A tetherable connection has come or gone.
+ * Uses {@code ConnectivityManager.EXTRA_AVAILABLE_TETHER},
+ * {@code ConnectivityManager.EXTRA_ACTIVE_TETHER} and
+ * {@code ConnectivityManager.EXTRA_ERRORED_TETHER} to indicate
+ * the current state of tethering. Each include a list of
+ * interface names in that state (may be empty).
* @hide
*/
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_TETHER_STATE_CHANGED =
"android.net.conn.TETHER_STATE_CHANGED";
/**
* @hide
- * gives a String[]
+ * gives a String[] listing all the interfaces configured for
+ * tethering and currently available for tethering.
*/
public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
/**
* @hide
- * gives a String[]
+ * gives a String[] listing all the interfaces currently tethered
+ * (ie, has dhcp support and packets potentially forwarded/NATed)
*/
public static final String EXTRA_ACTIVE_TETHER = "activeArray";
/**
* @hide
- * gives a String[]
+ * gives a String[] listing all the interfaces we tried to tether and
+ * failed. Use {@link #getLastTetherError} to find the error code
+ * for any interfaces listed here.
*/
public static final String EXTRA_ERRORED_TETHER = "erroredArray";
/**
- * The absence of APN..
+ * Broadcast Action: The captive portal tracker has finished its test.
+ * Sent only while running Setup Wizard, in lieu of showing a user
+ * notification.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_CAPTIVE_PORTAL_TEST_COMPLETED =
+ "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED";
+ /**
+ * The lookup key for a boolean that indicates whether a captive portal was detected.
+ * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+ * @hide
+ */
+ public static final String EXTRA_IS_CAPTIVE_PORTAL = "captivePortal";
+
+ /**
+ * The absence of a connection type.
* @hide
*/
public static final int TYPE_NONE = -1;
/**
- * The Default Mobile data connection. When active, all data traffic
- * will use this connection by default.
+ * The Mobile data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route)
*/
public static final int TYPE_MOBILE = 0;
/**
- * The Default WIFI data connection. When active, all data traffic
- * will use this connection by default.
+ * The WIFI data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route).
*/
public static final int TYPE_WIFI = 1;
/**
- * An MMS-specific Mobile data connection. This connection may be the
- * same as {@link #TYPE_MOBILE} but it may be different. This is used
- * by applications needing to talk to the carrier's Multimedia Messaging
- * Service servers. It may coexist with default data connections.
+ * An MMS-specific Mobile data connection. This network type may use the
+ * same network interface as {@link #TYPE_MOBILE} or it may use a different
+ * one. This is used by applications needing to talk to the carrier's
+ * Multimedia Messaging Service servers.
*/
public static final int TYPE_MOBILE_MMS = 2;
/**
- * A SUPL-specific Mobile data connection. This connection may be the
- * same as {@link #TYPE_MOBILE} but it may be different. This is used
- * by applications needing to talk to the carrier's Secure User Plane
- * Location servers for help locating the device. It may coexist with
- * default data connections.
+ * A SUPL-specific Mobile data connection. This network type may use the
+ * same network interface as {@link #TYPE_MOBILE} or it may use a different
+ * one. This is used by applications needing to talk to the carrier's
+ * Secure User Plane Location servers for help locating the device.
*/
public static final int TYPE_MOBILE_SUPL = 3;
/**
- * A DUN-specific Mobile data connection. This connection may be the
- * same as {@link #TYPE_MOBILE} but it may be different. This is used
- * by applicaitons performing a Dial Up Networking bridge so that
- * the carrier is aware of DUN traffic. It may coexist with default data
- * connections.
+ * A DUN-specific Mobile data connection. This network type may use the
+ * same network interface as {@link #TYPE_MOBILE} or it may use a different
+ * one. This is sometimes by the system when setting up an upstream connection
+ * for tethering so that the carrier is aware of DUN traffic.
*/
public static final int TYPE_MOBILE_DUN = 4;
/**
- * A High Priority Mobile data connection. This connection is typically
- * the same as {@link #TYPE_MOBILE} but the routing setup is different.
- * Only requesting processes will have access to the Mobile DNS servers
- * and only IP's explicitly requested via {@link #requestRouteToHost}
- * will route over this interface if a default route exists.
+ * A High Priority Mobile data connection. This network type uses the
+ * same network interface as {@link #TYPE_MOBILE} but the routing setup
+ * is different. Only requesting processes will have access to the
+ * Mobile DNS servers and only IP's explicitly requested via {@link #requestRouteToHost}
+ * will route over this interface if no default route exists.
*/
public static final int TYPE_MOBILE_HIPRI = 5;
/**
- * The Default WiMAX data connection. When active, all data traffic
- * will use this connection by default.
+ * The WiMAX data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route).
*/
public static final int TYPE_WIMAX = 6;
/**
- * The Default Bluetooth data connection. When active, all data traffic
- * will use this connection by default.
+ * The Bluetooth data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route).
*/
public static final int TYPE_BLUETOOTH = 7;
@@ -292,25 +324,26 @@ public class ConnectivityManager {
public static final int TYPE_DUMMY = 8;
/**
- * The Default Ethernet data connection. When active, all data traffic
- * will use this connection by default.
+ * The Ethernet data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route).
*/
public static final int TYPE_ETHERNET = 9;
/**
- * Over the air Adminstration.
+ * Over the air Administration.
* {@hide}
*/
public static final int TYPE_MOBILE_FOTA = 10;
/**
- * IP Multimedia Subsystem
+ * IP Multimedia Subsystem.
* {@hide}
*/
public static final int TYPE_MOBILE_IMS = 11;
/**
- * Carrier Branded Services
+ * Carrier Branded Services.
* {@hide}
*/
public static final int TYPE_MOBILE_CBS = 12;
@@ -328,11 +361,27 @@ public class ConnectivityManager {
/** {@hide} */
public static final int MAX_NETWORK_TYPE = TYPE_WIFI_P2P;
+ /**
+ * If you want to set the default network preference,you can directly
+ * change the networkAttributes array in framework's config.xml.
+ *
+ * @deprecated Since we support so many more networks now, the single
+ * network default network preference can't really express
+ * the hierarchy. Instead, the default is defined by the
+ * networkAttributes in config.xml. You can determine
+ * the current value by calling {@link #getNetworkPreference()}
+ * from an App.
+ */
+ @Deprecated
public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
/**
* Default value for {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY} in
- * milliseconds.
+ * milliseconds. This was introduced because IPv6 routes seem to take a
+ * moment to settle - trying network activity before the routes are adjusted
+ * can lead to packets using the wrong interface or having the wrong IP address.
+ * This delay is a bit crude, but in the future hopefully we will have kernel
+ * notifications letting us know when it's safe to use the new network.
*
* @hide
*/
@@ -340,11 +389,23 @@ public class ConnectivityManager {
private final IConnectivityManager mService;
+ /**
+ * Tests if a given integer represents a valid network type.
+ * @param networkType the type to be tested
+ * @return a boolean. {@code true} if the type is valid, else {@code false}
+ */
public static boolean isNetworkTypeValid(int networkType) {
return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
}
- /** {@hide} */
+ /**
+ * Returns a non-localized string representing a given network type.
+ * ONLY used for debugging output.
+ * @param type the type needing naming
+ * @return a String for the given type, or a string version of the type ("87")
+ * if no name is known.
+ * {@hide}
+ */
public static String getNetworkTypeName(int type) {
switch (type) {
case TYPE_MOBILE:
@@ -380,7 +441,13 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * Checks if a given type uses the cellular data connection.
+ * This should be replaced in the future by a network property.
+ * @param networkType the type to check
+ * @return a boolean - {@code true} if uses cellular network, else {@code false}
+ * {@hide}
+ */
public static boolean isNetworkTypeMobile(int networkType) {
switch (networkType) {
case TYPE_MOBILE:
@@ -397,6 +464,17 @@ public class ConnectivityManager {
}
}
+ /**
+ * Specifies the preferred network type. When the device has more
+ * than one type available the preferred network type will be used.
+ * Note that this made sense when we only had 2 network types,
+ * but with more and more default networks we need an array to list
+ * their ordering. This will be deprecated soon.
+ *
+ * @param preference the network type to prefer over all others. It is
+ * unspecified what happens to the old preferred network in the
+ * overall ordering.
+ */
public void setNetworkPreference(int preference) {
try {
mService.setNetworkPreference(preference);
@@ -404,6 +482,17 @@ public class ConnectivityManager {
}
}
+ /**
+ * Retrieves the current preferred network type.
+ * Note that this made sense when we only had 2 network types,
+ * but with more and more default networks we need an array to list
+ * their ordering. This will be deprecated soon.
+ *
+ * @return an integer representing the preferred network type
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
public int getNetworkPreference() {
try {
return mService.getNetworkPreference();
@@ -413,11 +502,16 @@ public class ConnectivityManager {
}
/**
- * Returns details about the currently active data network. When connected,
- * this network is the default route for outgoing connections. You should
- * always check {@link NetworkInfo#isConnected()} before initiating network
- * traffic. This may return {@code null} when no networks are available.
- * <p>This method requires the caller to hold the permission
+ * Returns details about the currently active default data network. When
+ * connected, this network is the default route for outgoing connections.
+ * You should always check {@link NetworkInfo#isConnected()} before initiating
+ * network traffic. This may return {@code null} when there is no default
+ * network.
+ *
+ * @return a {@link NetworkInfo} object for the current default network
+ * or {@code null} if no network default network is currently active
+ *
+ * <p>This method requires the call to hold the permission
* {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*/
public NetworkInfo getActiveNetworkInfo() {
@@ -428,7 +522,19 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * Returns details about the currently active default data network
+ * for a given uid. This is for internal use only to avoid spying
+ * other apps.
+ *
+ * @return a {@link NetworkInfo} object for the current default network
+ * for the given uid or {@code null} if no default network is
+ * available for the specified uid.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
+ * {@hide}
+ */
public NetworkInfo getActiveNetworkInfoForUid(int uid) {
try {
return mService.getActiveNetworkInfoForUid(uid);
@@ -437,6 +543,19 @@ public class ConnectivityManager {
}
}
+ /**
+ * Returns connection status information about a particular
+ * network type.
+ *
+ * @param networkType integer specifying which networkType in
+ * which you're interested.
+ * @return a {@link NetworkInfo} object for the requested
+ * network type or {@code null} if the type is not
+ * supported by the device.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
public NetworkInfo getNetworkInfo(int networkType) {
try {
return mService.getNetworkInfo(networkType);
@@ -445,6 +564,16 @@ public class ConnectivityManager {
}
}
+ /**
+ * Returns connection status information about all network
+ * types supported by the device.
+ *
+ * @return an array of {@link NetworkInfo} objects. Check each
+ * {@link NetworkInfo#getType} for which type each applies.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
public NetworkInfo[] getAllNetworkInfo() {
try {
return mService.getAllNetworkInfo();
@@ -453,7 +582,40 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * Returns details about the Provisioning or currently active default data network. When
+ * connected, this network is the default route for outgoing connections.
+ * You should always check {@link NetworkInfo#isConnected()} before initiating
+ * network traffic. This may return {@code null} when there is no default
+ * network.
+ *
+ * @return a {@link NetworkInfo} object for the current default network
+ * or {@code null} if no network default network is currently active
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ *
+ * {@hide}
+ */
+ public NetworkInfo getProvisioningOrActiveNetworkInfo() {
+ try {
+ return mService.getProvisioningOrActiveNetworkInfo();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the IP information for the current default network.
+ *
+ * @return a {@link LinkProperties} object describing the IP info
+ * for the current default network, or {@code null} if there
+ * is no current default network.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
public LinkProperties getActiveLinkProperties() {
try {
return mService.getActiveLinkProperties();
@@ -462,7 +624,18 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * Returns the IP information for a given network type.
+ *
+ * @param networkType the network type of interest.
+ * @return a {@link LinkProperties} object describing the IP info
+ * for the given networkType, or {@code null} if there is
+ * no current default network.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
public LinkProperties getLinkProperties(int networkType) {
try {
return mService.getLinkProperties(networkType);
@@ -471,7 +644,18 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * Tells each network type to set its radio power state as directed.
+ *
+ * @param turnOn a boolean, {@code true} to turn the radios on,
+ * {@code false} to turn them off.
+ * @return a boolean, {@code true} indicating success. All network types
+ * will be tried, even if some fail.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * {@hide}
+ */
public boolean setRadios(boolean turnOn) {
try {
return mService.setRadios(turnOn);
@@ -480,7 +664,18 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * Tells a given networkType to set its radio power state as directed.
+ *
+ * @param networkType the int networkType of interest.
+ * @param turnOn a boolean, {@code true} to turn the radio on,
+ * {@code} false to turn it off.
+ * @return a boolean, {@code true} indicating success.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * {@hide}
+ */
public boolean setRadio(int networkType, boolean turnOn) {
try {
return mService.setRadio(networkType, turnOn);
@@ -615,6 +810,9 @@ public class ConnectivityManager {
* network is active. Quota status can change rapidly, so these values
* shouldn't be cached.
*
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ *
* @hide
*/
public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
@@ -629,6 +827,9 @@ public class ConnectivityManager {
* Gets the value of the setting for enabling Mobile data.
*
* @return Whether mobile data is enabled.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* @hide
*/
public boolean getMobileDataEnabled() {
@@ -642,8 +843,8 @@ public class ConnectivityManager {
/**
* Sets the persisted value for enabling/disabling Mobile data.
*
- * @param enabled Whether the mobile data connection should be
- * used or not.
+ * @param enabled Whether the user wants the mobile data connection used
+ * or not.
* @hide
*/
public void setMobileDataEnabled(boolean enabled) {
@@ -666,6 +867,13 @@ public class ConnectivityManager {
}
/**
+ * Get the set of tetherable, available interfaces. This list is limited by
+ * device configuration and current interface existence.
+ *
+ * @return an array of 0 or more Strings of tetherable interface names.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetherableIfaces() {
@@ -677,6 +885,12 @@ public class ConnectivityManager {
}
/**
+ * Get the set of tethered interfaces.
+ *
+ * @return an array of 0 or more String of currently tethered interface names.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetheredIfaces() {
@@ -688,6 +902,18 @@ public class ConnectivityManager {
}
/**
+ * Get the set of interface names which attempted to tether but
+ * failed. Re-attempting to tether may cause them to reset to the Tethered
+ * state. Alternatively, causing the interface to be destroyed and recreated
+ * may cause them to reset to the available state.
+ * {@link ConnectivityManager#getLastTetherError} can be used to get more
+ * information on the cause of the errors.
+ *
+ * @return an array of 0 or more String indicating the interface names
+ * which failed to tether.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetheringErroredIfaces() {
@@ -699,7 +925,19 @@ public class ConnectivityManager {
}
/**
- * @return error A TETHER_ERROR value indicating success or failure type
+ * Attempt to tether the named interface. This will setup a dhcp server
+ * on the interface, forward and NAT IP packets and forward DNS requests
+ * to the best active upstream network interface. Note that if no upstream
+ * IP network interface is available, dhcp will still run and traffic will be
+ * allowed between the tethered devices and this device, though upstream net
+ * access will of course fail until an upstream network interface becomes
+ * active.
+ *
+ * @param iface the interface name to tether.
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* {@hide}
*/
public int tether(String iface) {
@@ -711,7 +949,13 @@ public class ConnectivityManager {
}
/**
- * @return error A TETHER_ERROR value indicating success or failure type
+ * Stop tethering the named interface.
+ *
+ * @param iface the interface name to untether.
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* {@hide}
*/
public int untether(String iface) {
@@ -723,6 +967,14 @@ public class ConnectivityManager {
}
/**
+ * Check if the device allows for tethering. It may be disabled via
+ * {@code ro.tether.denied} system property, {@link Settings#TETHER_SUPPORTED} or
+ * due to device configuration.
+ *
+ * @return a boolean - {@code true} indicating Tethering is supported.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public boolean isTetheringSupported() {
@@ -734,6 +986,15 @@ public class ConnectivityManager {
}
/**
+ * Get the list of regular expressions that define any tetherable
+ * USB network interfaces. If USB tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable usb interfaces.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetherableUsbRegexs() {
@@ -745,6 +1006,15 @@ public class ConnectivityManager {
}
/**
+ * Get the list of regular expressions that define any tetherable
+ * Wifi network interfaces. If Wifi tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable wifi interfaces.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetherableWifiRegexs() {
@@ -756,6 +1026,15 @@ public class ConnectivityManager {
}
/**
+ * Get the list of regular expressions that define any tetherable
+ * Bluetooth network interfaces. If Bluetooth tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable bluetooth interfaces.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetherableBluetoothRegexs() {
@@ -767,6 +1046,17 @@ public class ConnectivityManager {
}
/**
+ * Attempt to both alter the mode of USB and Tethering of USB. A
+ * utility method to deal with some of the complexity of USB - will
+ * attempt to switch to Rndis and subsequently tether the resulting
+ * interface on {@code true} or turn off tethering and switch off
+ * Rndis on {@code false}.
+ *
+ * @param enable a boolean - {@code true} to enable tethering
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* {@hide}
*/
public int setUsbTethering(boolean enable) {
@@ -801,9 +1091,15 @@ public class ConnectivityManager {
public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10;
/**
- * @param iface The name of the interface we're interested in
+ * Get a more detailed error code after a Tethering or Untethering
+ * request asynchronously failed.
+ *
+ * @param iface The name of the interface of interest
* @return error The error code of the last error tethering or untethering the named
* interface
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public int getLastTetherError(String iface) {
@@ -815,9 +1111,16 @@ public class ConnectivityManager {
}
/**
- * Ensure the device stays awake until we connect with the next network
- * @param forWhome The name of the network going down for logging purposes
+ * Try to ensure the device stays awake until we connect with the next network.
+ * Actually just holds a wakelock for a number of seconds while we try to connect
+ * to any default networks. This will expire if the timeout passes or if we connect
+ * to a default after this is called. For internal use only.
+ *
+ * @param forWhom the name of the network going down for logging purposes
* @return {@code true} on success, {@code false} on failure
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
* {@hide}
*/
public boolean requestNetworkTransitionWakelock(String forWhom) {
@@ -830,8 +1133,14 @@ public class ConnectivityManager {
}
/**
+ * Report network connectivity status. This is currently used only
+ * to alter status bar UI.
+ *
* @param networkType The type of network you want to report on
* @param percentage The quality of the connection 0 is bad, 100 is good
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#STATUS_BAR}.
* {@hide}
*/
public void reportInetCondition(int networkType, int percentage) {
@@ -842,7 +1151,16 @@ public class ConnectivityManager {
}
/**
- * @param proxyProperties The definition for the new global http proxy
+ * Set a network-independent global http proxy. This is not normally what you want
+ * for typical HTTP proxies - they are general network dependent. However if you're
+ * doing something unusual like general internal filtering this may be useful. On
+ * a private network where the proxy is not accessible, you may break HTTP using this.
+ *
+ * @param proxyProperties The a {@link ProxyProperites} object defining the new global
+ * HTTP proxy. A {@code null} value will clear the global HTTP proxy.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
* {@hide}
*/
public void setGlobalProxy(ProxyProperties p) {
@@ -853,7 +1171,13 @@ public class ConnectivityManager {
}
/**
- * @return proxyProperties for the current global proxy
+ * Retrieve any network-independent global HTTP proxy.
+ *
+ * @return {@link ProxyProperties} for the current global HTTP proxy or {@code null}
+ * if no global HTTP proxy is set.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public ProxyProperties getGlobalProxy() {
@@ -865,7 +1189,14 @@ public class ConnectivityManager {
}
/**
- * @return proxyProperties for the current proxy (global if set, network specific if not)
+ * Get the HTTP proxy settings for the current default network. Note that
+ * if a global proxy is set, it will override any per-network setting.
+ *
+ * @return the {@link ProxyProperties} for the current HTTP proxy, or {@code null} if no
+ * HTTP proxy is active.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public ProxyProperties getProxy() {
@@ -877,8 +1208,15 @@ public class ConnectivityManager {
}
/**
+ * Sets a secondary requirement bit for the given networkType.
+ * This requirement bit is generally under the control of the carrier
+ * or its agents and is not directly controlled by the user.
+ *
* @param networkType The network who's dependence has changed
- * @param met Boolean - true if network use is ok, false if not
+ * @param met Boolean - true if network use is OK, false if not
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
* {@hide}
*/
public void setDataDependency(int networkType, boolean met) {
@@ -892,11 +1230,15 @@ public class ConnectivityManager {
* Returns true if the hardware supports the given network type
* else it returns false. This doesn't indicate we have coverage
* or are authorized onto a network, just whether or not the
- * hardware supports it. For example a gsm phone without a sim
- * should still return true for mobile data, but a wifi only tablet
- * would return false.
- * @param networkType The nework type we'd like to check
- * @return true if supported, else false
+ * hardware supports it. For example a GSM phone without a SIM
+ * should still return {@code true} for mobile data, but a wifi only
+ * tablet would return {@code false}.
+ *
+ * @param networkType The network type we'd like to check
+ * @return {@code true} if supported, else {@code false}
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* @hide
*/
public boolean isNetworkSupported(int networkType) {
@@ -909,9 +1251,16 @@ public class ConnectivityManager {
/**
* Returns if the currently active data network is metered. A network is
* classified as metered when the user is sensitive to heavy data usage on
- * that connection. You should check this before doing large data transfers,
- * and warn the user or delay the operation until another network is
- * available.
+ * that connection due to monetary costs, data limitations or
+ * battery/performance issues. You should check this before doing large
+ * data transfers, and warn the user or delay the operation until another
+ * network is available.
+ *
+ * @return {@code true} if large transfers should be avoided, otherwise
+ * {@code false}.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*/
public boolean isActiveNetworkMetered() {
try {
@@ -921,7 +1270,15 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * If the LockdownVpn mechanism is enabled, updates the vpn
+ * with a reload of its profile.
+ *
+ * @return a boolean with {@code} indicating success
+ *
+ * <p>This method can only be called by the system UID
+ * {@hide}
+ */
public boolean updateLockdownVpn() {
try {
return mService.updateLockdownVpn();
@@ -931,6 +1288,14 @@ public class ConnectivityManager {
}
/**
+ * Signal that the captive portal check on the indicated network
+ * is complete and we can turn the network on for general use.
+ *
+ * @param info the {@link NetworkInfo} object for the networkType
+ * in question.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
* {@hide}
*/
public void captivePortalCheckComplete(NetworkInfo info) {
@@ -940,4 +1305,95 @@ public class ConnectivityManager {
}
}
+ /**
+ * Signal that the captive portal check on the indicated network
+ * is complete and whether its a captive portal or not.
+ *
+ * @param info the {@link NetworkInfo} object for the networkType
+ * in question.
+ * @param isCaptivePortal true/false.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
+ * {@hide}
+ */
+ public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
+ try {
+ mService.captivePortalCheckCompleted(info, isCaptivePortal);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Supply the backend messenger for a network tracker
+ *
+ * @param type NetworkType to set
+ * @param messenger {@link Messenger}
+ * {@hide}
+ */
+ public void supplyMessenger(int networkType, Messenger messenger) {
+ try {
+ mService.supplyMessenger(networkType, messenger);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Check mobile provisioning.
+ *
+ * @param suggestedTimeOutMs, timeout in milliseconds
+ *
+ * @return time out that will be used, maybe less that suggestedTimeOutMs
+ * -1 if an error.
+ *
+ * {@hide}
+ */
+ public int checkMobileProvisioning(int suggestedTimeOutMs) {
+ int timeOutMs = -1;
+ try {
+ timeOutMs = mService.checkMobileProvisioning(suggestedTimeOutMs);
+ } catch (RemoteException e) {
+ }
+ return timeOutMs;
+ }
+
+ /**
+ * Get the mobile provisioning url.
+ * {@hide}
+ */
+ public String getMobileProvisioningUrl() {
+ try {
+ return mService.getMobileProvisioningUrl();
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * Get the mobile redirected provisioning url.
+ * {@hide}
+ */
+ public String getMobileRedirectedProvisioningUrl() {
+ try {
+ return mService.getMobileRedirectedProvisioningUrl();
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * Set sign in error notification to visible or in visible
+ *
+ * @param visible
+ * @param networkType
+ *
+ * {@hide}
+ */
+ public void setProvisioningNotificationVisible(boolean visible, int networkType,
+ String extraInfo, String url) {
+ try {
+ mService.setProvisioningNotificationVisible(visible, networkType, extraInfo, url);
+ } catch (RemoteException e) {
+ }
+ }
}
diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java
index e2660e4..3bede5d 100644
--- a/core/java/android/net/DhcpInfo.java
+++ b/core/java/android/net/DhcpInfo.java
@@ -27,11 +27,10 @@ public class DhcpInfo implements Parcelable {
public int ipAddress;
public int gateway;
public int netmask;
-
public int dns1;
public int dns2;
-
public int serverAddress;
+
public int leaseDuration;
public DhcpInfo() {
diff --git a/core/java/android/net/DhcpInfoInternal.java b/core/java/android/net/DhcpInfoInternal.java
deleted file mode 100644
index f3508c1..0000000
--- a/core/java/android/net/DhcpInfoInternal.java
+++ /dev/null
@@ -1,166 +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.net;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * A simple object for retrieving the results of a DHCP request.
- * Replaces (internally) the IPv4-only DhcpInfo class.
- * @hide
- */
-public class DhcpInfoInternal {
- private final static String TAG = "DhcpInfoInternal";
- public String ipAddress;
- public int prefixLength;
-
- public String dns1;
- public String dns2;
-
- public String serverAddress;
- public int leaseDuration;
-
- /**
- * Vendor specific information (from RFC 2132).
- */
- public String vendorInfo;
-
- private Collection<RouteInfo> mRoutes;
-
- public DhcpInfoInternal() {
- mRoutes = new ArrayList<RouteInfo>();
- }
-
- public void addRoute(RouteInfo routeInfo) {
- mRoutes.add(routeInfo);
- }
-
- public Collection<RouteInfo> getRoutes() {
- return Collections.unmodifiableCollection(mRoutes);
- }
-
- private int convertToInt(String addr) {
- if (addr != null) {
- try {
- InetAddress inetAddress = NetworkUtils.numericToInetAddress(addr);
- if (inetAddress instanceof Inet4Address) {
- return NetworkUtils.inetAddressToInt(inetAddress);
- }
- } catch (IllegalArgumentException e) {}
- }
- return 0;
- }
-
- public DhcpInfo makeDhcpInfo() {
- DhcpInfo info = new DhcpInfo();
- info.ipAddress = convertToInt(ipAddress);
- for (RouteInfo route : mRoutes) {
- if (route.isDefaultRoute()) {
- info.gateway = convertToInt(route.getGateway().getHostAddress());
- break;
- }
- }
- try {
- InetAddress inetAddress = NetworkUtils.numericToInetAddress(ipAddress);
- info.netmask = NetworkUtils.prefixLengthToNetmaskInt(prefixLength);
- } catch (IllegalArgumentException e) {}
- info.dns1 = convertToInt(dns1);
- info.dns2 = convertToInt(dns2);
- info.serverAddress = convertToInt(serverAddress);
- info.leaseDuration = leaseDuration;
- return info;
- }
-
- public LinkAddress makeLinkAddress() {
- if (TextUtils.isEmpty(ipAddress)) {
- Log.e(TAG, "makeLinkAddress with empty ipAddress");
- return null;
- }
- return new LinkAddress(NetworkUtils.numericToInetAddress(ipAddress), prefixLength);
- }
-
- public LinkProperties makeLinkProperties() {
- LinkProperties p = new LinkProperties();
- p.addLinkAddress(makeLinkAddress());
- for (RouteInfo route : mRoutes) {
- p.addRoute(route);
- }
- //if empty, connectivity configures default DNS
- if (TextUtils.isEmpty(dns1) == false) {
- p.addDns(NetworkUtils.numericToInetAddress(dns1));
- } else {
- Log.d(TAG, "makeLinkProperties with empty dns1!");
- }
- if (TextUtils.isEmpty(dns2) == false) {
- p.addDns(NetworkUtils.numericToInetAddress(dns2));
- } else {
- Log.d(TAG, "makeLinkProperties with empty dns2!");
- }
- return p;
- }
-
- /* Updates the DHCP fields that need to be retained from
- * original DHCP request if the DHCP renewal shows them as
- * being empty
- */
- public void updateFromDhcpRequest(DhcpInfoInternal orig) {
- if (orig == null) return;
-
- if (TextUtils.isEmpty(dns1)) {
- dns1 = orig.dns1;
- }
-
- if (TextUtils.isEmpty(dns2)) {
- dns2 = orig.dns2;
- }
-
- if (mRoutes.size() == 0) {
- for (RouteInfo route : orig.getRoutes()) {
- addRoute(route);
- }
- }
- }
-
- /**
- * Test if this DHCP lease includes vendor hint that network link is
- * metered, and sensitive to heavy data transfers.
- */
- public boolean hasMeteredHint() {
- if (vendorInfo != null) {
- return vendorInfo.contains("ANDROID_METERED");
- } else {
- return false;
- }
- }
-
- public String toString() {
- String routeString = "";
- for (RouteInfo route : mRoutes) routeString += route.toString() + " | ";
- return "addr: " + ipAddress + "/" + prefixLength +
- " mRoutes: " + routeString +
- " dns: " + dns1 + "," + dns2 +
- " dhcpServer: " + serverAddress +
- " leaseDuration: " + leaseDuration;
- }
-}
diff --git a/core/java/android/net/DhcpResults.aidl b/core/java/android/net/DhcpResults.aidl
new file mode 100644
index 0000000..f4db3c3
--- /dev/null
+++ b/core/java/android/net/DhcpResults.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.net;
+
+parcelable DhcpResults;
diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
new file mode 100644
index 0000000..a3f70da
--- /dev/null
+++ b/core/java/android/net/DhcpResults.java
@@ -0,0 +1,247 @@
+/*
+ * 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.os.Parcelable;
+import android.os.Parcel;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * A simple object for retrieving the results of a DHCP request.
+ * Optimized (attempted) for that jni interface
+ * TODO - remove when DhcpInfo is deprecated. Move the remaining api to LinkProperties.
+ * @hide
+ */
+public class DhcpResults implements Parcelable {
+ private static final String TAG = "DhcpResults";
+
+ public final LinkProperties linkProperties;
+
+ public InetAddress serverAddress;
+
+ /**
+ * Vendor specific information (from RFC 2132).
+ */
+ public String vendorInfo;
+
+ public int leaseDuration;
+
+ public DhcpResults() {
+ linkProperties = new LinkProperties();
+ }
+
+ /** copy constructor */
+ public DhcpResults(DhcpResults source) {
+ if (source != null) {
+ linkProperties = new LinkProperties(source.linkProperties);
+ serverAddress = source.serverAddress;
+ leaseDuration = source.leaseDuration;
+ vendorInfo = source.vendorInfo;
+ } else {
+ linkProperties = new LinkProperties();
+ }
+ }
+
+ public DhcpResults(LinkProperties lp) {
+ linkProperties = new LinkProperties(lp);
+ }
+
+ /**
+ * Updates the DHCP fields that need to be retained from
+ * original DHCP request if the current renewal shows them
+ * being empty.
+ */
+ public void updateFromDhcpRequest(DhcpResults orig) {
+ if (orig == null || orig.linkProperties == null) return;
+ if (linkProperties.getRoutes().size() == 0) {
+ for (RouteInfo r : orig.linkProperties.getRoutes()) linkProperties.addRoute(r);
+ }
+ if (linkProperties.getDnses().size() == 0) {
+ for (InetAddress d : orig.linkProperties.getDnses()) linkProperties.addDns(d);
+ }
+ }
+
+ /**
+ * Test if this DHCP lease includes vendor hint that network link is
+ * metered, and sensitive to heavy data transfers.
+ */
+ public boolean hasMeteredHint() {
+ if (vendorInfo != null) {
+ return vendorInfo.contains("ANDROID_METERED");
+ } else {
+ return false;
+ }
+ }
+
+ public void clear() {
+ linkProperties.clear();
+ serverAddress = null;
+ vendorInfo = null;
+ leaseDuration = 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer str = new StringBuffer(linkProperties.toString());
+
+ str.append(" DHCP server ").append(serverAddress);
+ str.append(" Vendor info ").append(vendorInfo);
+ str.append(" lease ").append(leaseDuration).append(" seconds");
+
+ return str.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+
+ if (!(obj instanceof DhcpResults)) return false;
+
+ DhcpResults target = (DhcpResults)obj;
+
+ if (linkProperties == null) {
+ if (target.linkProperties != null) return false;
+ } else if (!linkProperties.equals(target.linkProperties)) return false;
+ if (serverAddress == null) {
+ if (target.serverAddress != null) return false;
+ } else if (!serverAddress.equals(target.serverAddress)) return false;
+ if (vendorInfo == null) {
+ if (target.vendorInfo != null) return false;
+ } else if (!vendorInfo.equals(target.vendorInfo)) return false;
+ if (leaseDuration != target.leaseDuration) return false;
+
+ return true;
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ linkProperties.writeToParcel(dest, flags);
+
+ dest.writeInt(leaseDuration);
+
+ if (serverAddress != null) {
+ dest.writeByte((byte)1);
+ dest.writeByteArray(serverAddress.getAddress());
+ } else {
+ dest.writeByte((byte)0);
+ }
+
+ dest.writeString(vendorInfo);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final Creator<DhcpResults> CREATOR =
+ new Creator<DhcpResults>() {
+ public DhcpResults createFromParcel(Parcel in) {
+ DhcpResults prop = new DhcpResults((LinkProperties)in.readParcelable(null));
+
+ prop.leaseDuration = in.readInt();
+
+ if (in.readByte() == 1) {
+ try {
+ prop.serverAddress = InetAddress.getByAddress(in.createByteArray());
+ } catch (UnknownHostException e) {}
+ }
+
+ prop.vendorInfo = in.readString();
+
+ return prop;
+ }
+
+ public DhcpResults[] newArray(int size) {
+ return new DhcpResults[size];
+ }
+ };
+
+ // Utils for jni population - false on success
+ public void setInterfaceName(String interfaceName) {
+ linkProperties.setInterfaceName(interfaceName);
+ }
+
+ public boolean addLinkAddress(String addrString, int prefixLength) {
+ InetAddress addr;
+ try {
+ addr = NetworkUtils.numericToInetAddress(addrString);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "addLinkAddress failed with addrString " + addrString);
+ return true;
+ }
+
+ LinkAddress linkAddress = new LinkAddress(addr, prefixLength);
+ linkProperties.addLinkAddress(linkAddress);
+
+ RouteInfo routeInfo = new RouteInfo(linkAddress);
+ linkProperties.addRoute(routeInfo);
+ return false;
+ }
+
+ public boolean addGateway(String addrString) {
+ try {
+ linkProperties.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(addrString)));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "addGateway failed with addrString " + addrString);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean addDns(String addrString) {
+ if (TextUtils.isEmpty(addrString) == false) {
+ try {
+ linkProperties.addDns(NetworkUtils.numericToInetAddress(addrString));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "addDns failed with addrString " + addrString);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean setServerAddress(String addrString) {
+ try {
+ serverAddress = NetworkUtils.numericToInetAddress(addrString);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "setServerAddress failed with addrString " + addrString);
+ return true;
+ }
+ return false;
+ }
+
+ public void setLeaseDuration(int duration) {
+ leaseDuration = duration;
+ }
+
+ public void setVendorInfo(String info) {
+ vendorInfo = info;
+ }
+
+ public void setDomains(String domains) {
+ linkProperties.setDomains(domains);
+ }
+}
diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java
index 8dc900e..1ebf393 100644
--- a/core/java/android/net/DhcpStateMachine.java
+++ b/core/java/android/net/DhcpStateMachine.java
@@ -26,7 +26,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.net.DhcpInfoInternal;
+import android.net.DhcpResults;
import android.net.NetworkUtils;
import android.os.Message;
import android.os.PowerManager;
@@ -64,7 +64,7 @@ public class DhcpStateMachine extends StateMachine {
private static final String WAKELOCK_TAG = "DHCP";
//Remember DHCP configuration from first request
- private DhcpInfoInternal mDhcpInfo;
+ private DhcpResults mDhcpResults;
private static final int DHCP_RENEW = 0;
private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW";
@@ -77,7 +77,7 @@ public class DhcpStateMachine extends StateMachine {
RENEW
};
- private String mInterfaceName;
+ private final String mInterfaceName;
private boolean mRegisteredForPreDhcpNotification = false;
private static final int BASE = Protocol.BASE_DHCP;
@@ -348,23 +348,21 @@ public class DhcpStateMachine extends StateMachine {
private boolean runDhcp(DhcpAction dhcpAction) {
boolean success = false;
- DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
+ DhcpResults dhcpResults = new DhcpResults();
if (dhcpAction == DhcpAction.START) {
/* Stop any existing DHCP daemon before starting new */
NetworkUtils.stopDhcp(mInterfaceName);
if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName);
- success = NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal);
- mDhcpInfo = dhcpInfoInternal;
+ success = NetworkUtils.runDhcp(mInterfaceName, dhcpResults);
} else if (dhcpAction == DhcpAction.RENEW) {
if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName);
- success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpInfoInternal);
- dhcpInfoInternal.updateFromDhcpRequest(mDhcpInfo);
+ success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpResults);
+ if (success) dhcpResults.updateFromDhcpRequest(mDhcpResults);
}
-
if (success) {
if (DBG) Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
- long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion
+ long leaseDuration = dhcpResults.leaseDuration; //int to long conversion
//Sanity check for renewal
if (leaseDuration >= 0) {
@@ -384,7 +382,8 @@ public class DhcpStateMachine extends StateMachine {
//infinite lease time, no renewal needed
}
- mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
+ mDhcpResults = dhcpResults;
+ mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpResults)
.sendToTarget();
} else {
Log.e(TAG, "DHCP failed on " + mInterfaceName + ": " +
diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java
index 39440c2..ee738fd 100644
--- a/core/java/android/net/DummyDataStateTracker.java
+++ b/core/java/android/net/DummyDataStateTracker.java
@@ -19,6 +19,7 @@ package android.net;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
+import android.os.Messenger;
import android.util.Slog;
/**
@@ -119,10 +120,16 @@ public class DummyDataStateTracker implements NetworkStateTracker {
return true;
}
+ @Override
public void captivePortalCheckComplete() {
// not implemented
}
+ @Override
+ public void captivePortalCheckCompleted(boolean isCaptivePortal) {
+ // not implemented
+ }
+
/**
* Record the detailed state of a network, and if it is a
* change from the previous state, send a notification to
@@ -203,6 +210,21 @@ public class DummyDataStateTracker implements NetworkStateTracker {
// not supported on this network
}
+ @Override
+ public void addStackedLink(LinkProperties link) {
+ mLinkProperties.addStackedLink(link);
+ }
+
+ @Override
+ public void removeStackedLink(LinkProperties link) {
+ mLinkProperties.removeStackedLink(link);
+ }
+
+ @Override
+ public void supplyMessenger(Messenger messenger) {
+ // not supported on this network
+ }
+
static private void log(String s) {
Slog.d(TAG, s);
}
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index 37601fc..ac2b0d9 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -22,6 +22,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Message;
+import android.os.Messenger;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
@@ -170,13 +171,12 @@ public class EthernetDataTracker implements NetworkStateTracker {
private void runDhcp() {
Thread dhcpThread = new Thread(new Runnable() {
public void run() {
- DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
- if (!NetworkUtils.runDhcp(mIface, dhcpInfoInternal)) {
+ DhcpResults dhcpResults = new DhcpResults();
+ if (!NetworkUtils.runDhcp(mIface, dhcpResults)) {
Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
return;
}
- mLinkProperties = dhcpInfoInternal.makeLinkProperties();
- mLinkProperties.setInterfaceName(mIface);
+ mLinkProperties = dhcpResults.linkProperties;
mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
@@ -279,6 +279,11 @@ public class EthernetDataTracker implements NetworkStateTracker {
// not implemented
}
+ @Override
+ public void captivePortalCheckCompleted(boolean isCaptivePortal) {
+ // not implemented
+ }
+
/**
* Turn the wireless radio off for a network.
* @param turnOn {@code true} to turn the radio on, {@code false}
@@ -408,4 +413,19 @@ public class EthernetDataTracker implements NetworkStateTracker {
public void setDependencyMet(boolean met) {
// not supported on this network
}
+
+ @Override
+ public void addStackedLink(LinkProperties link) {
+ mLinkProperties.addStackedLink(link);
+ }
+
+ @Override
+ public void removeStackedLink(LinkProperties link) {
+ mLinkProperties.removeStackedLink(link);
+ }
+
+ @Override
+ public void supplyMessenger(Messenger messenger) {
+ // not supported on this network
+ }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 056fa03..a17b4f5 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -22,7 +22,9 @@ import android.net.NetworkQuotaInfo;
import android.net.NetworkState;
import android.net.ProxyProperties;
import android.os.IBinder;
+import android.os.Messenger;
import android.os.ParcelFileDescriptor;
+import android.os.ResultReceiver;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
@@ -44,6 +46,8 @@ interface IConnectivityManager
NetworkInfo getNetworkInfo(int networkType);
NetworkInfo[] getAllNetworkInfo();
+ NetworkInfo getProvisioningOrActiveNetworkInfo();
+
boolean isNetworkSupported(int networkType);
LinkProperties getActiveLinkProperties();
@@ -126,4 +130,18 @@ interface IConnectivityManager
boolean updateLockdownVpn();
void captivePortalCheckComplete(in NetworkInfo info);
+
+ void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal);
+
+ void supplyMessenger(int networkType, in Messenger messenger);
+
+ int findConnectionTypeForIface(in String iface);
+
+ int checkMobileProvisioning(int suggestedTimeOutMs);
+
+ String getMobileProvisioningUrl();
+
+ String getMobileRedirectedProvisioningUrl();
+
+ void setProvisioningNotificationVisible(boolean visible, int networkType, in String extraInfo, in String url);
}
diff --git a/core/java/android/net/IThrottleManager.aidl b/core/java/android/net/IThrottleManager.aidl
deleted file mode 100644
index a12469d..0000000
--- a/core/java/android/net/IThrottleManager.aidl
+++ /dev/null
@@ -1,40 +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.net;
-
-import android.os.IBinder;
-
-/**
- * Interface that answers queries about data transfer amounts and throttling
- */
-/** {@hide} */
-interface IThrottleManager
-{
- long getByteCount(String iface, int dir, int period, int ago);
-
- int getThrottle(String iface);
-
- long getResetTime(String iface);
-
- long getPeriodStartTime(String iface);
-
- long getCliffThreshold(String iface, int cliff);
-
- int getCliffLevel(String iface, int cliff);
-
- String getHelpUri();
-}
diff --git a/core/java/android/net/LinkCapabilities.java b/core/java/android/net/LinkCapabilities.java
index eb9166f..fb444ea 100644
--- a/core/java/android/net/LinkCapabilities.java
+++ b/core/java/android/net/LinkCapabilities.java
@@ -314,8 +314,8 @@ public class LinkCapabilities implements Parcelable {
sb.append(":\"");
sb.append(entry.getValue());
sb.append("\"");
- return mCapabilities.toString();
}
+ sb.append("}");
return sb.toString();
}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index bf411cc..dc9a54f 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -22,10 +22,13 @@ import android.os.Parcel;
import android.text.TextUtils;
import java.net.InetAddress;
+import java.net.Inet4Address;
+
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Hashtable;
/**
* Describes the properties of a network link.
@@ -47,16 +50,27 @@ import java.util.Collections;
* don't care which is used. The gateways will be
* selected based on the destination address and the
* source address has no relavence.
+ *
+ * Links can also be stacked on top of each other.
+ * This can be used, for example, to represent a tunnel
+ * interface that runs on top of a physical interface.
+ *
* @hide
*/
public class LinkProperties implements Parcelable {
-
- String mIfaceName;
+ // The interface described by the network link.
+ private String mIfaceName;
private Collection<LinkAddress> mLinkAddresses = new ArrayList<LinkAddress>();
private Collection<InetAddress> mDnses = new ArrayList<InetAddress>();
+ private String mDomains;
private Collection<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
private ProxyProperties mHttpProxy;
+ // Stores the properties of links that are "stacked" above this link.
+ // Indexed by interface name to allow modification and to prevent duplicates being added.
+ private Hashtable<String, LinkProperties> mStackedLinks =
+ new Hashtable<String, LinkProperties>();
+
public static class CompareResult<T> {
public Collection<T> removed = new ArrayList<T>();
public Collection<T> added = new ArrayList<T>();
@@ -82,20 +96,38 @@ public class LinkProperties implements Parcelable {
mIfaceName = source.getInterfaceName();
for (LinkAddress l : source.getLinkAddresses()) mLinkAddresses.add(l);
for (InetAddress i : source.getDnses()) mDnses.add(i);
+ mDomains = source.getDomains();
for (RouteInfo r : source.getRoutes()) mRoutes.add(r);
mHttpProxy = (source.getHttpProxy() == null) ?
- null : new ProxyProperties(source.getHttpProxy());
+ null : new ProxyProperties(source.getHttpProxy());
+ for (LinkProperties l: source.mStackedLinks.values()) {
+ addStackedLink(l);
+ }
}
}
public void setInterfaceName(String iface) {
mIfaceName = iface;
+ ArrayList<RouteInfo> newRoutes = new ArrayList<RouteInfo>(mRoutes.size());
+ for (RouteInfo route : mRoutes) {
+ newRoutes.add(routeWithInterface(route));
+ }
+ mRoutes = newRoutes;
}
public String getInterfaceName() {
return mIfaceName;
}
+ public Collection<String> getAllInterfaceNames() {
+ Collection interfaceNames = new ArrayList<String>(mStackedLinks.size() + 1);
+ if (mIfaceName != null) interfaceNames.add(new String(mIfaceName));
+ for (LinkProperties stacked: mStackedLinks.values()) {
+ interfaceNames.addAll(stacked.getAllInterfaceNames());
+ }
+ return interfaceNames;
+ }
+
public Collection<InetAddress> getAddresses() {
Collection<InetAddress> addresses = new ArrayList<InetAddress>();
for (LinkAddress linkAddress : mLinkAddresses) {
@@ -130,14 +162,53 @@ public class LinkProperties implements Parcelable {
return Collections.unmodifiableCollection(mDnses);
}
+ public String getDomains() {
+ return mDomains;
+ }
+
+ public void setDomains(String domains) {
+ mDomains = domains;
+ }
+
+ private RouteInfo routeWithInterface(RouteInfo route) {
+ return new RouteInfo(
+ route.getDestination(),
+ route.getGateway(),
+ mIfaceName);
+ }
+
public void addRoute(RouteInfo route) {
- if (route != null) mRoutes.add(route);
+ if (route != null) {
+ String routeIface = route.getInterface();
+ if (routeIface != null && !routeIface.equals(mIfaceName)) {
+ throw new IllegalArgumentException(
+ "Route added with non-matching interface: " + routeIface +
+ " vs. " + mIfaceName);
+ }
+ mRoutes.add(routeWithInterface(route));
+ }
}
+
+ /**
+ * Returns all the routes on this link.
+ */
public Collection<RouteInfo> getRoutes() {
return Collections.unmodifiableCollection(mRoutes);
}
/**
+ * Returns all the routes on this link and all the links stacked above it.
+ */
+ public Collection<RouteInfo> getAllRoutes() {
+ Collection<RouteInfo> routes = new ArrayList();
+ routes.addAll(mRoutes);
+ for (LinkProperties stacked: mStackedLinks.values()) {
+ routes.addAll(stacked.getAllRoutes());
+ }
+ return routes;
+ }
+
+ /**
* Replaces the RouteInfos on this link with the given collection of RouteInfos.
*/
public void setRoutes(Collection<RouteInfo> routes) {
@@ -154,12 +225,54 @@ public class LinkProperties implements Parcelable {
return mHttpProxy;
}
+ /**
+ * Adds a stacked link.
+ *
+ * If there is already a stacked link with the same interfacename as link,
+ * that link is replaced with link. Otherwise, link is added to the list
+ * of stacked links. If link is null, nothing changes.
+ *
+ * @param link The link to add.
+ */
+ public void addStackedLink(LinkProperties link) {
+ if (link != null && link.getInterfaceName() != null) {
+ mStackedLinks.put(link.getInterfaceName(), link);
+ }
+ }
+
+ /**
+ * Removes a stacked link.
+ *
+ * If there a stacked link with the same interfacename as link, it is
+ * removed. Otherwise, nothing changes.
+ *
+ * @param link The link to add.
+ */
+ public void removeStackedLink(LinkProperties link) {
+ if (link != null && link.getInterfaceName() != null) {
+ mStackedLinks.remove(link.getInterfaceName());
+ }
+ }
+
+ /**
+ * Returns all the links stacked on top of this link.
+ */
+ public Collection<LinkProperties> getStackedLinks() {
+ Collection<LinkProperties> stacked = new ArrayList<LinkProperties>();
+ for (LinkProperties link : mStackedLinks.values()) {
+ stacked.add(new LinkProperties(link));
+ }
+ return Collections.unmodifiableCollection(stacked);
+ }
+
public void clear() {
mIfaceName = null;
mLinkAddresses.clear();
mDnses.clear();
+ mDomains = null;
mRoutes.clear();
mHttpProxy = null;
+ mStackedLinks.clear();
}
/**
@@ -182,12 +295,36 @@ public class LinkProperties implements Parcelable {
for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
dns += "] ";
- String routes = "Routes: [";
+ String domainName = "Domains: " + mDomains;
+
+ String routes = " Routes: [";
for (RouteInfo route : mRoutes) routes += route.toString() + ",";
routes += "] ";
String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
- return ifaceName + linkAddresses + routes + dns + proxy;
+ String stacked = "";
+ if (mStackedLinks.values().size() > 0) {
+ stacked += " Stacked: [";
+ for (LinkProperties link: mStackedLinks.values()) {
+ stacked += " [" + link.toString() + " ],";
+ }
+ stacked += "] ";
+ }
+ return "{" + ifaceName + linkAddresses + routes + dns + domainName + proxy + stacked + "}";
+ }
+
+ /**
+ * Returns true if this link has an IPv4 address.
+ *
+ * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
+ */
+ public boolean hasIPv4Address() {
+ for (LinkAddress address : mLinkAddresses) {
+ if (address.getAddress() instanceof Inet4Address) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -201,7 +338,7 @@ public class LinkProperties implements Parcelable {
}
/**
- * Compares this {@code LinkProperties} interface name against the target
+ * Compares this {@code LinkProperties} interface addresses against the target
*
* @param target LinkProperties to compare.
* @return {@code true} if both are identical, {@code false} otherwise.
@@ -221,6 +358,12 @@ public class LinkProperties implements Parcelable {
*/
public boolean isIdenticalDnses(LinkProperties target) {
Collection<InetAddress> targetDnses = target.getDnses();
+ String targetDomains = target.getDomains();
+ if (mDomains == null) {
+ if (targetDomains != null) return false;
+ } else {
+ if (mDomains.equals(targetDomains) == false) return false;
+ }
return (mDnses.size() == targetDnses.size()) ?
mDnses.containsAll(targetDnses) : false;
}
@@ -248,6 +391,26 @@ public class LinkProperties implements Parcelable {
getHttpProxy().equals(target.getHttpProxy());
}
+ /**
+ * Compares this {@code LinkProperties} stacked links against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ */
+ public boolean isIdenticalStackedLinks(LinkProperties target) {
+ if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) {
+ return false;
+ }
+ for (LinkProperties stacked : mStackedLinks.values()) {
+ // Hashtable values can never be null.
+ String iface = stacked.getInterfaceName();
+ if (!stacked.equals(target.mStackedLinks.get(iface))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
@Override
/**
* Compares this {@code LinkProperties} instance against the target
@@ -260,6 +423,10 @@ public class LinkProperties implements Parcelable {
* 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal.
* 2. Worst case performance is O(n^2).
*
+ * This method does not check that stacked interfaces are equal, because
+ * stacked interfaces are not so much a property of the link as a
+ * description of connections between links.
+ *
* @param obj the object to be tested for equality.
* @return {@code true} if both objects are equal, {@code false} otherwise.
*/
@@ -274,7 +441,8 @@ public class LinkProperties implements Parcelable {
isIdenticalAddresses(target) &&
isIdenticalDnses(target) &&
isIdenticalRoutes(target) &&
- isIdenticalHttpProxy(target);
+ isIdenticalHttpProxy(target) &&
+ isIdenticalStackedLinks(target);
}
/**
@@ -350,16 +518,16 @@ public class LinkProperties implements Parcelable {
public CompareResult<RouteInfo> compareRoutes(LinkProperties target) {
/*
* Duplicate the RouteInfos into removed, we will be removing
- * routes which are common between mDnses and target
+ * routes which are common between mRoutes and target
* leaving the routes that are different. And route address which
* are in target but not in mRoutes are placed in added.
*/
CompareResult<RouteInfo> result = new CompareResult<RouteInfo>();
- result.removed = new ArrayList<RouteInfo>(mRoutes);
+ result.removed = getAllRoutes();
result.added.clear();
if (target != null) {
- for (RouteInfo r : target.getRoutes()) {
+ for (RouteInfo r : target.getAllRoutes()) {
if (! result.removed.remove(r)) {
result.added.add(r);
}
@@ -379,13 +547,14 @@ public class LinkProperties implements Parcelable {
return ((null == mIfaceName) ? 0 : mIfaceName.hashCode()
+ mLinkAddresses.size() * 31
+ mDnses.size() * 37
+ + ((null == mDomains) ? 0 : mDomains.hashCode())
+ mRoutes.size() * 41
- + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode()));
+ + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode())
+ + mStackedLinks.hashCode() * 47);
}
/**
* Implement the Parcelable interface.
- * @hide
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(getInterfaceName());
@@ -398,6 +567,7 @@ public class LinkProperties implements Parcelable {
for(InetAddress d : mDnses) {
dest.writeByteArray(d.getAddress());
}
+ dest.writeString(mDomains);
dest.writeInt(mRoutes.size());
for(RouteInfo route : mRoutes) {
@@ -410,23 +580,21 @@ public class LinkProperties implements Parcelable {
} else {
dest.writeByte((byte)0);
}
+ ArrayList<LinkProperties> stackedLinks = new ArrayList(mStackedLinks.values());
+ dest.writeList(stackedLinks);
}
/**
* Implement the Parcelable interface.
- * @hide
*/
public static final Creator<LinkProperties> CREATOR =
new Creator<LinkProperties>() {
public LinkProperties createFromParcel(Parcel in) {
LinkProperties netProp = new LinkProperties();
+
String iface = in.readString();
if (iface != null) {
- try {
- netProp.setInterfaceName(iface);
- } catch (Exception e) {
- return null;
- }
+ netProp.setInterfaceName(iface);
}
int addressCount = in.readInt();
for (int i=0; i<addressCount; i++) {
@@ -438,6 +606,7 @@ public class LinkProperties implements Parcelable {
netProp.addDns(InetAddress.getByAddress(in.createByteArray()));
} catch (UnknownHostException e) { }
}
+ netProp.setDomains(in.readString());
addressCount = in.readInt();
for (int i=0; i<addressCount; i++) {
netProp.addRoute((RouteInfo)in.readParcelable(null));
@@ -445,6 +614,11 @@ public class LinkProperties implements Parcelable {
if (in.readByte() == 1) {
netProp.setHttpProxy((ProxyProperties)in.readParcelable(null));
}
+ ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>();
+ in.readList(stackedLinks, LinkProperties.class.getClassLoader());
+ for (LinkProperties stackedLink: stackedLinks) {
+ netProp.addStackedLink(stackedLink);
+ }
return netProp;
}
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
index 6c36a7d..3b43c36 100644
--- a/core/java/android/net/LocalSocketImpl.java
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -136,8 +136,28 @@ class LocalSocketImpl
write_native(b, myFd);
}
}
+
+ /**
+ * Wait until the data in sending queue is emptied. A polling version
+ * for flush implementation.
+ * @throws IOException
+ * if an i/o error occurs.
+ */
+ @Override
+ public void flush() throws IOException {
+ FileDescriptor myFd = fd;
+ if (myFd == null) throw new IOException("socket closed");
+ while(pending_native(myFd) > 0) {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException ie) {
+ return;
+ }
+ }
+ }
}
+ private native int pending_native(FileDescriptor fd) throws IOException;
private native int available_native(FileDescriptor fd) throws IOException;
private native void close_native(FileDescriptor fd) throws IOException;
private native int read_native(FileDescriptor fd) throws IOException;
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index b35d61c..b2b5314 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -40,6 +40,7 @@ import com.android.internal.util.AsyncChannel;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Track the state of mobile data connectivity. This is done by
@@ -51,7 +52,7 @@ import java.io.PrintWriter;
public class MobileDataStateTracker implements NetworkStateTracker {
private static final String TAG = "MobileDataStateTracker";
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private static final boolean VDBG = false;
private PhoneConstants.DataState mMobileDataState;
@@ -74,7 +75,8 @@ public class MobileDataStateTracker implements NetworkStateTracker {
private Handler mHandler;
private AsyncChannel mDataConnectionTrackerAc;
- private Messenger mMessenger;
+
+ private AtomicBoolean mIsCaptivePortal = new AtomicBoolean(false);
/**
* Create a new MobileDataStateTracker
@@ -102,8 +104,8 @@ public class MobileDataStateTracker implements NetworkStateTracker {
IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+ filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN);
filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
- filter.addAction(DctConstants.ACTION_DATA_CONNECTION_TRACKER_MESSENGER);
mContext.registerReceiver(new MobileDataStateReceiver(), filter);
mMobileDataState = PhoneConstants.DataState.DISCONNECTED;
@@ -170,20 +172,55 @@ public class MobileDataStateTracker implements NetworkStateTracker {
public void releaseWakeLock() {
}
+ private void updateLinkProperitesAndCapatilities(Intent intent) {
+ mLinkProperties = intent.getParcelableExtra(
+ PhoneConstants.DATA_LINK_PROPERTIES_KEY);
+ if (mLinkProperties == null) {
+ loge("CONNECTED event did not supply link properties.");
+ mLinkProperties = new LinkProperties();
+ }
+ mLinkCapabilities = intent.getParcelableExtra(
+ PhoneConstants.DATA_LINK_CAPABILITIES_KEY);
+ if (mLinkCapabilities == null) {
+ loge("CONNECTED event did not supply link capabilities.");
+ mLinkCapabilities = new LinkCapabilities();
+ }
+ }
+
private class MobileDataStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(TelephonyIntents.
- ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
+ ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN)) {
+ String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
- if (VDBG) {
- log(String.format("Broadcast received: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED"
- + "mApnType=%s %s received apnType=%s", mApnType,
- TextUtils.equals(apnType, mApnType) ? "==" : "!=", apnType));
+ if (!TextUtils.equals(mApnType, apnType)) {
+ return;
}
+ if (DBG) {
+ log("Broadcast received: " + intent.getAction() + " apnType=" + apnType
+ + " apnName=" + apnName);
+ }
+
+ // Make us in the connecting state until we make a new TYPE_MOBILE_PROVISIONING
+ mMobileDataState = PhoneConstants.DataState.CONNECTING;
+ updateLinkProperitesAndCapatilities(intent);
+ mNetworkInfo.setIsConnectedToProvisioningNetwork(true);
+
+ // Change state to SUSPENDED so setDetailedState
+ // sends EVENT_STATE_CHANGED to connectivityService
+ setDetailedState(DetailedState.SUSPENDED, "", apnName);
+ } else if (intent.getAction().equals(TelephonyIntents.
+ ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
+ String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
if (!TextUtils.equals(apnType, mApnType)) {
return;
}
+ // Assume this isn't a provisioning network.
+ mNetworkInfo.setIsConnectedToProvisioningNetwork(false);
+ if (DBG) {
+ log("Broadcast received: " + intent.getAction() + " apnType=" + apnType);
+ }
int oldSubtype = mNetworkInfo.getSubtype();
int newSubType = TelephonyManager.getDefault().getNetworkType();
@@ -201,7 +238,7 @@ public class MobileDataStateTracker implements NetworkStateTracker {
String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
mNetworkInfo.setRoaming(intent.getBooleanExtra(
PhoneConstants.DATA_NETWORK_ROAMING_KEY, false));
- if (VDBG) {
+ if (DBG) {
log(mApnType + " setting isAvailable to " +
intent.getBooleanExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY,false));
}
@@ -235,18 +272,7 @@ public class MobileDataStateTracker implements NetworkStateTracker {
setDetailedState(DetailedState.SUSPENDED, reason, apnName);
break;
case CONNECTED:
- mLinkProperties = intent.getParcelableExtra(
- PhoneConstants.DATA_LINK_PROPERTIES_KEY);
- if (mLinkProperties == null) {
- loge("CONNECTED event did not supply link properties.");
- mLinkProperties = new LinkProperties();
- }
- mLinkCapabilities = intent.getParcelableExtra(
- PhoneConstants.DATA_LINK_CAPABILITIES_KEY);
- if (mLinkCapabilities == null) {
- loge("CONNECTED event did not supply link capabilities.");
- mLinkCapabilities = new LinkCapabilities();
- }
+ updateLinkProperitesAndCapatilities(intent);
setDetailedState(DetailedState.CONNECTED, reason, apnName);
break;
}
@@ -271,27 +297,17 @@ public class MobileDataStateTracker implements NetworkStateTracker {
equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
if (!TextUtils.equals(apnType, mApnType)) {
- if (DBG) {
- log(String.format(
- "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " +
- "mApnType=%s != received apnType=%s", mApnType, apnType));
- }
return;
}
+ // Assume this isn't a provisioning network.
+ mNetworkInfo.setIsConnectedToProvisioningNetwork(false);
String reason = intent.getStringExtra(PhoneConstants.FAILURE_REASON_KEY);
String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
if (DBG) {
- log("Received " + intent.getAction() +
- " broadcast" + reason == null ? "" : "(" + reason + ")");
+ log("Broadcast received: " + intent.getAction() +
+ " reason=" + reason == null ? "null" : reason);
}
setDetailedState(DetailedState.FAILED, reason, apnName);
- } else if (intent.getAction().equals(DctConstants
- .ACTION_DATA_CONNECTION_TRACKER_MESSENGER)) {
- if (VDBG) log(mApnType + " got ACTION_DATA_CONNECTION_TRACKER_MESSENGER");
- mMessenger =
- intent.getParcelableExtra(DctConstants.EXTRA_MESSENGER);
- AsyncChannel ac = new AsyncChannel();
- ac.connect(mContext, MobileDataStateTracker.this.mHandler, mMessenger);
} else {
if (DBG) log("Broadcast received: ignore " + intent.getAction());
}
@@ -381,11 +397,27 @@ public class MobileDataStateTracker implements NetworkStateTracker {
return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED);
}
+ /**
+ * @return true if this is ready to operate
+ */
+ public boolean isReady() {
+ return mDataConnectionTrackerAc != null;
+ }
+
@Override
public void captivePortalCheckComplete() {
// not implemented
}
+ @Override
+ public void captivePortalCheckCompleted(boolean isCaptivePortal) {
+ if (mIsCaptivePortal.getAndSet(isCaptivePortal) != isCaptivePortal) {
+ // Captive portal change enable/disable failing fast
+ setEnableFailFastMobileData(
+ isCaptivePortal ? DctConstants.ENABLED : DctConstants.DISABLED);
+ }
+ }
+
/**
* Record the detailed state of a network, and if it is a
* change from the previous state, send a notification to
@@ -503,6 +535,19 @@ public class MobileDataStateTracker implements NetworkStateTracker {
}
/**
+ * Eanble/disable FailFast
+ *
+ * @param enabled is DctConstants.ENABLED/DISABLED
+ */
+ public void setEnableFailFastMobileData(int enabled) {
+ if (DBG) log("setEnableFailFastMobileData(enabled=" + enabled + ")");
+ final AsyncChannel channel = mDataConnectionTrackerAc;
+ if (channel != null) {
+ channel.sendMessage(DctConstants.CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA, enabled);
+ }
+ }
+
+ /**
* carrier dependency is met/unmet
* @param met
*/
@@ -521,6 +566,50 @@ public class MobileDataStateTracker implements NetworkStateTracker {
}
}
+ /**
+ * Inform DCT mobile provisioning has started, it ends when provisioning completes.
+ */
+ public void enableMobileProvisioning(String url) {
+ if (DBG) log("enableMobileProvisioning(url=" + url + ")");
+ final AsyncChannel channel = mDataConnectionTrackerAc;
+ if (channel != null) {
+ Message msg = Message.obtain();
+ msg.what = DctConstants.CMD_ENABLE_MOBILE_PROVISIONING;
+ msg.setData(Bundle.forPair(DctConstants.PROVISIONING_URL_KEY, url));
+ channel.sendMessage(msg);
+ }
+ }
+
+ /**
+ * Return if this network is the provisioning network. Valid only if connected.
+ * @param met
+ */
+ public boolean isProvisioningNetwork() {
+ boolean retVal;
+ try {
+ Message msg = Message.obtain();
+ msg.what = DctConstants.CMD_IS_PROVISIONING_APN;
+ msg.setData(Bundle.forPair(DctConstants.APN_TYPE_KEY, mApnType));
+ Message result = mDataConnectionTrackerAc.sendMessageSynchronously(msg);
+ retVal = result.arg1 == DctConstants.ENABLED;
+ } catch (NullPointerException e) {
+ loge("isProvisioningNetwork: X " + e);
+ retVal = false;
+ }
+ if (DBG) log("isProvisioningNetwork: retVal=" + retVal);
+ return retVal;
+ }
+
+ @Override
+ public void addStackedLink(LinkProperties link) {
+ mLinkProperties.addStackedLink(link);
+ }
+
+ @Override
+ public void removeStackedLink(LinkProperties link) {
+ mLinkProperties.removeStackedLink(link);
+ }
+
@Override
public String toString() {
final CharArrayWriter writer = new CharArrayWriter();
@@ -603,6 +692,12 @@ public class MobileDataStateTracker implements NetworkStateTracker {
return new LinkCapabilities(mLinkCapabilities);
}
+ public void supplyMessenger(Messenger messenger) {
+ if (VDBG) log(mApnType + " got supplyMessenger");
+ AsyncChannel ac = new AsyncChannel();
+ ac.connect(mContext, MobileDataStateTracker.this.mHandler, messenger);
+ }
+
private void log(String s) {
Slog.d(TAG, mApnType + ": " + s);
}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 0b23cb7..4d2a70d 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -19,6 +19,8 @@ package android.net;
import android.os.Parcelable;
import android.os.Parcel;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.EnumMap;
/**
@@ -81,7 +83,7 @@ public class NetworkInfo implements Parcelable {
/** Link has poor connectivity. */
VERIFYING_POOR_LINK,
/** Checking if network is a captive portal */
- CAPTIVE_PORTAL_CHECK,
+ CAPTIVE_PORTAL_CHECK
}
/**
@@ -118,6 +120,8 @@ public class NetworkInfo implements Parcelable {
private String mExtraInfo;
private boolean mIsFailover;
private boolean mIsRoaming;
+ private boolean mIsConnectedToProvisioningNetwork;
+
/**
* Indicates whether network connectivity is possible:
*/
@@ -146,6 +150,7 @@ public class NetworkInfo implements Parcelable {
mState = State.UNKNOWN;
mIsAvailable = false; // until we're told otherwise, assume unavailable
mIsRoaming = false;
+ mIsConnectedToProvisioningNetwork = false;
}
/** {@hide} */
@@ -162,6 +167,7 @@ public class NetworkInfo implements Parcelable {
mIsFailover = source.mIsFailover;
mIsRoaming = source.mIsRoaming;
mIsAvailable = source.mIsAvailable;
+ mIsConnectedToProvisioningNetwork = source.mIsConnectedToProvisioningNetwork;
}
}
@@ -312,12 +318,30 @@ public class NetworkInfo implements Parcelable {
}
}
- void setRoaming(boolean isRoaming) {
+ /** {@hide} */
+ @VisibleForTesting
+ public void setRoaming(boolean isRoaming) {
synchronized (this) {
mIsRoaming = isRoaming;
}
}
+ /** {@hide} */
+ @VisibleForTesting
+ public boolean isConnectedToProvisioningNetwork() {
+ synchronized (this) {
+ return mIsConnectedToProvisioningNetwork;
+ }
+ }
+
+ /** {@hide} */
+ @VisibleForTesting
+ public void setIsConnectedToProvisioningNetwork(boolean val) {
+ synchronized (this) {
+ mIsConnectedToProvisioningNetwork = val;
+ }
+ }
+
/**
* Reports the current coarse-grained state of the network.
* @return the coarse-grained state
@@ -401,7 +425,9 @@ public class NetworkInfo implements Parcelable {
append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo).
append(", roaming: ").append(mIsRoaming).
append(", failover: ").append(mIsFailover).
- append(", isAvailable: ").append(mIsAvailable);
+ append(", isAvailable: ").append(mIsAvailable).
+ append(", isConnectedToProvisioningNetwork: ").
+ append(mIsConnectedToProvisioningNetwork);
return builder.toString();
}
}
@@ -429,6 +455,7 @@ public class NetworkInfo implements Parcelable {
dest.writeInt(mIsFailover ? 1 : 0);
dest.writeInt(mIsAvailable ? 1 : 0);
dest.writeInt(mIsRoaming ? 1 : 0);
+ dest.writeInt(mIsConnectedToProvisioningNetwork ? 1 : 0);
dest.writeString(mReason);
dest.writeString(mExtraInfo);
}
@@ -451,6 +478,7 @@ public class NetworkInfo implements Parcelable {
netInfo.mIsFailover = in.readInt() != 0;
netInfo.mIsAvailable = in.readInt() != 0;
netInfo.mIsRoaming = in.readInt() != 0;
+ netInfo.mIsConnectedToProvisioningNetwork = in.readInt() != 0;
netInfo.mReason = in.readString();
netInfo.mExtraInfo = in.readString();
return netInfo;
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 0a0c1e0..9ed7533 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -18,6 +18,9 @@ package android.net;
import android.content.Context;
import android.os.Handler;
+import android.os.Messenger;
+
+import static com.android.internal.util.Protocol.BASE_NETWORK_STATE_TRACKER;
/**
* Interface provides the {@link com.android.server.ConnectivityService}
@@ -48,25 +51,38 @@ public interface NetworkStateTracker {
* msg.what = EVENT_STATE_CHANGED
* msg.obj = NetworkInfo object
*/
- public static final int EVENT_STATE_CHANGED = 1;
+ public static final int EVENT_STATE_CHANGED = BASE_NETWORK_STATE_TRACKER;
/**
* msg.what = EVENT_CONFIGURATION_CHANGED
* msg.obj = NetworkInfo object
*/
- public static final int EVENT_CONFIGURATION_CHANGED = 3;
+ public static final int EVENT_CONFIGURATION_CHANGED = BASE_NETWORK_STATE_TRACKER + 1;
/**
* msg.what = EVENT_RESTORE_DEFAULT_NETWORK
* msg.obj = FeatureUser object
*/
- public static final int EVENT_RESTORE_DEFAULT_NETWORK = 6;
+ public static final int EVENT_RESTORE_DEFAULT_NETWORK = BASE_NETWORK_STATE_TRACKER + 2;
/**
* msg.what = EVENT_NETWORK_SUBTYPE_CHANGED
* msg.obj = NetworkInfo object
*/
- public static final int EVENT_NETWORK_SUBTYPE_CHANGED = 7;
+ public static final int EVENT_NETWORK_SUBTYPE_CHANGED = BASE_NETWORK_STATE_TRACKER + 3;
+
+ /**
+ * msg.what = EVENT_NETWORK_CONNECTED
+ * msg.obj = LinkProperties object
+ */
+ public static final int EVENT_NETWORK_CONNECTED = BASE_NETWORK_STATE_TRACKER + 4;
+
+ /**
+ * msg.what = EVENT_NETWORK_CONNECTION_DISCONNECTED
+ * msg.obj = LinkProperties object, same iface name
+ */
+ public static final int EVENT_NETWORK_DISCONNECTED = BASE_NETWORK_STATE_TRACKER + 5;
+
/**
* -------------------------------------------------------------
@@ -128,6 +144,11 @@ public interface NetworkStateTracker {
public void captivePortalCheckComplete();
/**
+ * Captive portal check has completed
+ */
+ public void captivePortalCheckCompleted(boolean isCaptive);
+
+ /**
* Turn the wireless radio off for a network.
* @param turnOn {@code true} to turn the radio on, {@code false}
*/
@@ -197,4 +218,20 @@ public interface NetworkStateTracker {
* An external dependency has been met/unmet
*/
public void setDependencyMet(boolean met);
+
+ /**
+ * Informs the state tracker that another interface is stacked on top of it.
+ **/
+ public void addStackedLink(LinkProperties link);
+
+ /**
+ * Informs the state tracker that a stacked interface has been removed.
+ **/
+ public void removeStackedLink(LinkProperties link);
+
+ /*
+ * Called once to setup async channel between this and
+ * the underlying network specific code.
+ */
+ public void supplyMessenger(Messenger messenger);
}
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index c757605..9cb904d 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -135,6 +135,18 @@ public class NetworkStats implements Parcelable {
builder.append(" operations=").append(operations);
return builder.toString();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof Entry) {
+ final Entry e = (Entry) o;
+ return uid == e.uid && set == e.set && tag == e.tag && rxBytes == e.rxBytes
+ && rxPackets == e.rxPackets && txBytes == e.txBytes
+ && txPackets == e.txPackets && operations == e.operations
+ && iface.equals(e.iface);
+ }
+ return false;
+ }
}
public NetworkStats(long elapsedRealtime, int initialSize) {
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index 382b25e..62d8738 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -639,6 +639,7 @@ public class NetworkStatsHistory implements Parcelable {
@Deprecated
public static long[] readFullLongArray(DataInputStream in) throws IOException {
final int size = in.readInt();
+ if (size < 0) throw new ProtocolException("negative array size");
final long[] values = new long[size];
for (int i = 0; i < values.length; i++) {
values[i] = in.readLong();
@@ -680,6 +681,7 @@ public class NetworkStatsHistory implements Parcelable {
public static long[] readVarLongArray(DataInputStream in) throws IOException {
final int size = in.readInt();
if (size == -1) return null;
+ if (size < 0) throw new ProtocolException("negative array size");
final long[] values = new long[size];
for (int i = 0; i < values.length; i++) {
values[i] = readVarLong(in);
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index d3839ad..c189ba4 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -22,6 +22,7 @@ import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.NetworkIdentity.COMBINE_SUBTYPE_ENABLED;
import static android.net.NetworkIdentity.scrubSubscriberId;
+import static android.net.wifi.WifiInfo.removeDoubleQuotes;
import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G;
import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G;
import static android.telephony.TelephonyManager.NETWORK_CLASS_4_G;
@@ -279,7 +280,8 @@ public class NetworkTemplate implements Parcelable {
private boolean matchesWifi(NetworkIdentity ident) {
switch (ident.mType) {
case TYPE_WIFI:
- return Objects.equal(mNetworkId, ident.mNetworkId);
+ return Objects.equal(
+ removeDoubleQuotes(mNetworkId), removeDoubleQuotes(ident.mNetworkId));
default:
return false;
}
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index d39e741..4ab479e 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -62,21 +62,21 @@ public class NetworkUtils {
* addresses. This call blocks until it obtains a result (either success
* or failure) from the daemon.
* @param interfaceName the name of the interface to configure
- * @param ipInfo if the request succeeds, this object is filled in with
+ * @param dhcpResults if the request succeeds, this object is filled in with
* the IP address information.
* @return {@code true} for success, {@code false} for failure
*/
- public native static boolean runDhcp(String interfaceName, DhcpInfoInternal ipInfo);
+ public native static boolean runDhcp(String interfaceName, DhcpResults dhcpResults);
/**
* Initiate renewal on the Dhcp client daemon. This call blocks until it obtains
* a result (either success or failure) from the daemon.
* @param interfaceName the name of the interface to configure
- * @param ipInfo if the request succeeds, this object is filled in with
+ * @param dhcpResults if the request succeeds, this object is filled in with
* the IP address information.
* @return {@code true} for success, {@code false} for failure
*/
- public native static boolean runDhcpRenew(String interfaceName, DhcpInfoInternal ipInfo);
+ public native static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults);
/**
* Shut down the DHCP client daemon.
@@ -124,12 +124,9 @@ public class NetworkUtils {
* @param inetAddr is an InetAddress corresponding to the IPv4 address
* @return the IP address as an integer in network byte order
*/
- public static int inetAddressToInt(InetAddress inetAddr)
+ public static int inetAddressToInt(Inet4Address inetAddr)
throws IllegalArgumentException {
byte [] addr = inetAddr.getAddress();
- if (addr.length != 4) {
- throw new IllegalArgumentException("Not an IPv4 address");
- }
return ((addr[3] & 0xff) << 24) | ((addr[2] & 0xff) << 16) |
((addr[1] & 0xff) << 8) | (addr[0] & 0xff);
}
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 275f32a..1d051dd 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -29,6 +29,17 @@ import java.util.Collection;
/**
* A simple container for route information.
*
+ * In order to be used, a route must have a destination prefix and:
+ *
+ * - A gateway address (next-hop, for gatewayed routes), or
+ * - An interface (for directly-connected routes), or
+ * - Both a gateway and an interface.
+ *
+ * This class does not enforce these constraints because there is code that
+ * uses RouteInfo objects to store directly-connected routes without interfaces.
+ * Such objects cannot be used directly, but can be put into a LinkProperties
+ * object which then specifies the interface.
+ *
* @hide
*/
public class RouteInfo implements Parcelable {
@@ -42,10 +53,31 @@ public class RouteInfo implements Parcelable {
*/
private final InetAddress mGateway;
+ /**
+ * The interface for this route.
+ */
+ private final String mInterface;
+
private final boolean mIsDefault;
private final boolean mIsHost;
+ private final boolean mHasGateway;
- public RouteInfo(LinkAddress destination, InetAddress gateway) {
+ /**
+ * Constructs a RouteInfo object.
+ *
+ * If destination is null, then gateway must be specified and the
+ * constructed route is either the IPv4 default route <code>0.0.0.0</code>
+ * if @gateway is an instance of {@link Inet4Address}, or the IPv6 default
+ * route <code>::/0</code> if gateway is an instance of
+ * {@link Inet6Address}.
+ *
+ * destination and gateway may not both be null.
+ *
+ * @param destination the destination prefix
+ * @param gateway the IP address to route packets through
+ * @param iface the interface name to send packets on
+ */
+ public RouteInfo(LinkAddress destination, InetAddress gateway, String iface) {
if (destination == null) {
if (gateway != null) {
if (gateway instanceof Inet4Address) {
@@ -55,7 +87,8 @@ public class RouteInfo implements Parcelable {
}
} else {
// no destination, no gateway. invalid.
- throw new RuntimeException("Invalid arguments passed in.");
+ throw new IllegalArgumentException("Invalid arguments passed in: " + gateway + "," +
+ destination);
}
}
if (gateway == null) {
@@ -65,33 +98,47 @@ public class RouteInfo implements Parcelable {
gateway = Inet6Address.ANY;
}
}
+ mHasGateway = (!gateway.isAnyLocalAddress());
+
mDestination = new LinkAddress(NetworkUtils.getNetworkPart(destination.getAddress(),
destination.getNetworkPrefixLength()), destination.getNetworkPrefixLength());
mGateway = gateway;
+ mInterface = iface;
mIsDefault = isDefault();
mIsHost = isHost();
}
+ public RouteInfo(LinkAddress destination, InetAddress gateway) {
+ this(destination, gateway, null);
+ }
+
public RouteInfo(InetAddress gateway) {
- this(null, gateway);
+ this(null, gateway, null);
+ }
+
+ public RouteInfo(LinkAddress host) {
+ this(host, null, null);
}
- public static RouteInfo makeHostRoute(InetAddress host) {
- return makeHostRoute(host, null);
+ public static RouteInfo makeHostRoute(InetAddress host, String iface) {
+ return makeHostRoute(host, null, iface);
}
- public static RouteInfo makeHostRoute(InetAddress host, InetAddress gateway) {
+ public static RouteInfo makeHostRoute(InetAddress host, InetAddress gateway, String iface) {
if (host == null) return null;
if (host instanceof Inet4Address) {
- return new RouteInfo(new LinkAddress(host, 32), gateway);
+ return new RouteInfo(new LinkAddress(host, 32), gateway, iface);
} else {
- return new RouteInfo(new LinkAddress(host, 128), gateway);
+ return new RouteInfo(new LinkAddress(host, 128), gateway, iface);
}
}
private boolean isHost() {
- return (mGateway.equals(Inet4Address.ANY) || mGateway.equals(Inet6Address.ANY));
+ return (mDestination.getAddress() instanceof Inet4Address &&
+ mDestination.getNetworkPrefixLength() == 32) ||
+ (mDestination.getAddress() instanceof Inet6Address &&
+ mDestination.getNetworkPrefixLength() == 128);
}
private boolean isDefault() {
@@ -115,6 +162,10 @@ public class RouteInfo implements Parcelable {
return mGateway;
}
+ public String getInterface() {
+ return mInterface;
+ }
+
public boolean isDefaultRoute() {
return mIsDefault;
}
@@ -123,6 +174,10 @@ public class RouteInfo implements Parcelable {
return mIsHost;
}
+ public boolean hasGateway() {
+ return mHasGateway;
+ }
+
public String toString() {
String val = "";
if (mDestination != null) val = mDestination.toString();
@@ -149,6 +204,8 @@ public class RouteInfo implements Parcelable {
dest.writeByte((byte) 1);
dest.writeByteArray(mGateway.getAddress());
}
+
+ dest.writeString(mInterface);
}
@Override
@@ -167,14 +224,19 @@ public class RouteInfo implements Parcelable {
target.getGateway() == null
: mGateway.equals(target.getGateway());
- return sameDestination && sameAddress
+ boolean sameInterface = (mInterface == null) ?
+ target.getInterface() == null
+ : mInterface.equals(target.getInterface());
+
+ return sameDestination && sameAddress && sameInterface
&& mIsDefault == target.mIsDefault;
}
@Override
public int hashCode() {
- return (mDestination == null ? 0 : mDestination.hashCode())
- + (mGateway == null ? 0 :mGateway.hashCode())
+ return (mDestination == null ? 0 : mDestination.hashCode() * 41)
+ + (mGateway == null ? 0 :mGateway.hashCode() * 47)
+ + (mInterface == null ? 0 :mInterface.hashCode() * 67)
+ (mIsDefault ? 3 : 7);
}
@@ -202,13 +264,15 @@ public class RouteInfo implements Parcelable {
} catch (UnknownHostException e) {}
}
+ String iface = in.readString();
+
LinkAddress dest = null;
if (destAddr != null) {
dest = new LinkAddress(destAddr, prefix);
}
- return new RouteInfo(dest, gateway);
+ return new RouteInfo(dest, gateway, iface);
}
public RouteInfo[] newArray(int size) {
@@ -216,13 +280,9 @@ public class RouteInfo implements Parcelable {
}
};
- private boolean matches(InetAddress destination) {
+ protected boolean matches(InetAddress destination) {
if (destination == null) return false;
- // if the destination is present and the route is default.
- // return true
- if (isDefault()) return true;
-
// match the route destination and destination with prefix length
InetAddress dstNet = NetworkUtils.getNetworkPart(destination,
mDestination.getNetworkPrefixLength());
diff --git a/core/java/android/net/ThrottleManager.java b/core/java/android/net/ThrottleManager.java
deleted file mode 100644
index 5fdac58..0000000
--- a/core/java/android/net/ThrottleManager.java
+++ /dev/null
@@ -1,214 +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.net;
-
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.os.Binder;
-import android.os.RemoteException;
-
-/**
- * Class that handles throttling. It provides read/write numbers per interface
- * and methods to apply throttled rates.
- * {@hide}
- */
-public class ThrottleManager
-{
- /**
- * Broadcast each polling period to indicate new data counts.
- *
- * Includes four extras:
- * EXTRA_CYCLE_READ - a long of the read bytecount for the current cycle
- * EXTRA_CYCLE_WRITE -a long of the write bytecount for the current cycle
- * EXTRA_CYLCE_START -a long of MS for the cycle start time
- * EXTRA_CYCLE_END -a long of MS for the cycle stop time
- * {@hide}
- */
- public static final String THROTTLE_POLL_ACTION = "android.net.thrott.POLL_ACTION";
- /**
- * The lookup key for a long for the read bytecount for this period. Retrieve with
- * {@link android.content.Intent#getLongExtra(String)}.
- * {@hide}
- */
- public static final String EXTRA_CYCLE_READ = "cycleRead";
- /**
- * contains a long of the number of bytes written in the cycle
- * {@hide}
- */
- public static final String EXTRA_CYCLE_WRITE = "cycleWrite";
- /**
- * contains a long of the number of bytes read in the cycle
- * {@hide}
- */
- public static final String EXTRA_CYCLE_START = "cycleStart";
- /**
- * contains a long of the ms since 1970 used to init a calendar, etc for the end
- * of the cycle
- * {@hide}
- */
- public static final String EXTRA_CYCLE_END = "cycleEnd";
-
- /**
- * Broadcast when the thottle level changes.
- * {@hide}
- */
- public static final String THROTTLE_ACTION = "android.net.thrott.THROTTLE_ACTION";
- /**
- * int of the current bandwidth in TODO
- * {@hide}
- */
- public static final String EXTRA_THROTTLE_LEVEL = "level";
-
- /**
- * Broadcast on boot and whenever the settings change.
- * {@hide}
- */
- public static final String POLICY_CHANGED_ACTION = "android.net.thrott.POLICY_CHANGED_ACTION";
-
- // {@hide}
- public static final int DIRECTION_TX = 0;
- // {@hide}
- public static final int DIRECTION_RX = 1;
-
- // {@hide}
- public static final int PERIOD_CYCLE = 0;
- // {@hide}
- public static final int PERIOD_YEAR = 1;
- // {@hide}
- public static final int PERIOD_MONTH = 2;
- // {@hide}
- public static final int PERIOD_WEEK = 3;
- // @hide
- public static final int PERIOD_7DAY = 4;
- // @hide
- public static final int PERIOD_DAY = 5;
- // @hide
- public static final int PERIOD_24HOUR = 6;
- // @hide
- public static final int PERIOD_HOUR = 7;
- // @hide
- public static final int PERIOD_60MIN = 8;
- // @hide
- public static final int PERIOD_MINUTE = 9;
- // @hide
- public static final int PERIOD_60SEC = 10;
- // @hide
- public static final int PERIOD_SECOND = 11;
-
-
-
- /**
- * returns a long of the ms from the epoch to the time the current cycle ends for the
- * named interface
- * {@hide}
- */
- public long getResetTime(String iface) {
- try {
- return mService.getResetTime(iface);
- } catch (RemoteException e) {
- return -1;
- }
- }
-
- /**
- * returns a long of the ms from the epoch to the time the current cycle started for the
- * named interface
- * {@hide}
- */
- public long getPeriodStartTime(String iface) {
- try {
- return mService.getPeriodStartTime(iface);
- } catch (RemoteException e) {
- return -1;
- }
- }
-
- /**
- * returns a long of the byte count either read or written on the named interface
- * for the period described. Direction is either DIRECTION_RX or DIRECTION_TX and
- * period may only be PERIOD_CYCLE for the current cycle (other periods may be supported
- * in the future). Ago indicates the number of periods in the past to lookup - 0 means
- * the current period, 1 is the last one, 2 was two periods ago..
- * {@hide}
- */
- public long getByteCount(String iface, int direction, int period, int ago) {
- try {
- return mService.getByteCount(iface, direction, period, ago);
- } catch (RemoteException e) {
- return -1;
- }
- }
-
- /**
- * returns the number of bytes read+written after which a particular cliff
- * takes effect on the named iface. Currently only cliff #1 is supported (1 step)
- * {@hide}
- */
- public long getCliffThreshold(String iface, int cliff) {
- try {
- return mService.getCliffThreshold(iface, cliff);
- } catch (RemoteException e) {
- return -1;
- }
- }
-
- /**
- * returns the thottling bandwidth (bps) for a given cliff # on the named iface.
- * only cliff #1 is currently supported.
- * {@hide}
- */
- public int getCliffLevel(String iface, int cliff) {
- try {
- return mService.getCliffLevel(iface, cliff);
- } catch (RemoteException e) {
- return -1;
- }
- }
-
- /**
- * returns the help URI for throttling
- * {@hide}
- */
- public String getHelpUri() {
- try {
- return mService.getHelpUri();
- } catch (RemoteException e) {
- return null;
- }
- }
-
-
- private IThrottleManager mService;
-
- /**
- * Don't allow use of default constructor.
- */
- @SuppressWarnings({"UnusedDeclaration"})
- private ThrottleManager() {
- }
-
- /**
- * {@hide}
- */
- public ThrottleManager(IThrottleManager service) {
- if (service == null) {
- throw new IllegalArgumentException(
- "ThrottleManager() cannot be constructed with null service");
- }
- mService = service;
- }
-}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index e437d2e..786439e 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -119,6 +119,8 @@ public class TrafficStats {
* Tags between {@code 0xFFFFFF00} and {@code 0xFFFFFFFF} are reserved and
* used internally by system services like {@link DownloadManager} when
* performing traffic on behalf of an application.
+ *
+ * @see #clearThreadStatsTag()
*/
public static void setThreadStatsTag(int tag) {
NetworkManagementSocketTagger.setThreadSocketStatsTag(tag);
@@ -128,11 +130,19 @@ public class TrafficStats {
* Get the active tag used when accounting {@link Socket} traffic originating
* from the current thread. Only one active tag per thread is supported.
* {@link #tagSocket(Socket)}.
+ *
+ * @see #setThreadStatsTag(int)
*/
public static int getThreadStatsTag() {
return NetworkManagementSocketTagger.getThreadSocketStatsTag();
}
+ /**
+ * Clear any active tag set to account {@link Socket} traffic originating
+ * from the current thread.
+ *
+ * @see #setThreadStatsTag(int)
+ */
public static void clearThreadStatsTag() {
NetworkManagementSocketTagger.setThreadSocketStatsTag(-1);
}
@@ -148,7 +158,7 @@ public class TrafficStats {
* To take effect, caller must hold
* {@link android.Manifest.permission#UPDATE_DEVICE_STATS} permission.
*
- * {@hide}
+ * @hide
*/
public static void setThreadStatsUid(int uid) {
NetworkManagementSocketTagger.setThreadSocketStatsUid(uid);
@@ -260,10 +270,13 @@ public class TrafficStats {
}
/**
- * Get the total number of packets transmitted through the mobile interface.
- *
- * @return number of packets. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of packets transmitted across mobile networks since device
+ * boot. Counts packets across all mobile network interfaces, and always
+ * increases monotonically since device boot. Statistics are measured at the
+ * network layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getMobileTxPackets() {
long total = 0;
@@ -274,10 +287,13 @@ public class TrafficStats {
}
/**
- * Get the total number of packets received through the mobile interface.
- *
- * @return number of packets. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of packets received across mobile networks since device
+ * boot. Counts packets across all mobile network interfaces, and always
+ * increases monotonically since device boot. Statistics are measured at the
+ * network layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getMobileRxPackets() {
long total = 0;
@@ -288,10 +304,13 @@ public class TrafficStats {
}
/**
- * Get the total number of bytes transmitted through the mobile interface.
- *
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of bytes transmitted across mobile networks since device
+ * boot. Counts packets across all mobile network interfaces, and always
+ * increases monotonically since device boot. Statistics are measured at the
+ * network layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getMobileTxBytes() {
long total = 0;
@@ -302,10 +321,13 @@ public class TrafficStats {
}
/**
- * Get the total number of bytes received through the mobile interface.
- *
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of bytes received across mobile networks since device boot.
+ * Counts packets across all mobile network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getMobileRxBytes() {
long total = 0;
@@ -315,252 +337,260 @@ public class TrafficStats {
return total;
}
- /**
- * Get the total number of packets transmitted through the specified interface.
- *
- * @return number of packets. If the statistics are not supported by this interface,
- * {@link #UNSUPPORTED} will be returned.
- * @hide
- */
+ /** {@hide} */
+ public static long getMobileTcpRxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ final long stat = nativeGetIfaceStat(iface, TYPE_TCP_RX_PACKETS);
+ if (stat != UNSUPPORTED) {
+ total += stat;
+ }
+ }
+ return total;
+ }
+
+ /** {@hide} */
+ public static long getMobileTcpTxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ final long stat = nativeGetIfaceStat(iface, TYPE_TCP_TX_PACKETS);
+ if (stat != UNSUPPORTED) {
+ total += stat;
+ }
+ }
+ return total;
+ }
+
+ /** {@hide} */
public static long getTxPackets(String iface) {
return nativeGetIfaceStat(iface, TYPE_TX_PACKETS);
}
- /**
- * Get the total number of packets received through the specified interface.
- *
- * @return number of packets. If the statistics are not supported by this interface,
- * {@link #UNSUPPORTED} will be returned.
- * @hide
- */
+ /** {@hide} */
public static long getRxPackets(String iface) {
return nativeGetIfaceStat(iface, TYPE_RX_PACKETS);
}
- /**
- * Get the total number of bytes transmitted through the specified interface.
- *
- * @return number of bytes. If the statistics are not supported by this interface,
- * {@link #UNSUPPORTED} will be returned.
- * @hide
- */
+ /** {@hide} */
public static long getTxBytes(String iface) {
return nativeGetIfaceStat(iface, TYPE_TX_BYTES);
}
- /**
- * Get the total number of bytes received through the specified interface.
- *
- * @return number of bytes. If the statistics are not supported by this interface,
- * {@link #UNSUPPORTED} will be returned.
- * @hide
- */
+ /** {@hide} */
public static long getRxBytes(String iface) {
return nativeGetIfaceStat(iface, TYPE_RX_BYTES);
}
/**
- * Get the total number of packets sent through all network interfaces.
- *
- * @return the number of packets. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of packets transmitted since device boot. Counts packets
+ * across all network interfaces, and always increases monotonically since
+ * device boot. Statistics are measured at the network layer, so they
+ * include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getTotalTxPackets() {
return nativeGetTotalStat(TYPE_TX_PACKETS);
}
/**
- * Get the total number of packets received through all network interfaces.
- *
- * @return number of packets. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of packets received since device boot. Counts packets
+ * across all network interfaces, and always increases monotonically since
+ * device boot. Statistics are measured at the network layer, so they
+ * include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getTotalRxPackets() {
return nativeGetTotalStat(TYPE_RX_PACKETS);
}
/**
- * Get the total number of bytes sent through all network interfaces.
- *
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of bytes transmitted since device boot. Counts packets
+ * across all network interfaces, and always increases monotonically since
+ * device boot. Statistics are measured at the network layer, so they
+ * include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getTotalTxBytes() {
return nativeGetTotalStat(TYPE_TX_BYTES);
}
/**
- * Get the total number of bytes received through all network interfaces.
- *
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * Return number of bytes received since device boot. Counts packets across
+ * all network interfaces, and always increases monotonically since device
+ * boot. Statistics are measured at the network layer, so they include both
+ * TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
+ * return {@link #UNSUPPORTED} on devices where statistics aren't available.
*/
public static long getTotalRxBytes() {
return nativeGetTotalStat(TYPE_RX_BYTES);
}
/**
- * Get the number of bytes sent through the network for this UID.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
+ * Return number of bytes transmitted by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
*
- * @param uid The UID of the process to examine.
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
*/
- public static native long getUidTxBytes(int uid);
+ public static long getUidTxBytes(int uid) {
+ return nativeGetUidStat(uid, TYPE_TX_BYTES);
+ }
/**
- * Get the number of bytes received through the network for this UID.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
+ * Return number of bytes received by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
*
- * @param uid The UID of the process to examine.
- * @return number of bytes
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
*/
- public static native long getUidRxBytes(int uid);
+ public static long getUidRxBytes(int uid) {
+ return nativeGetUidStat(uid, TYPE_RX_BYTES);
+ }
/**
- * Get the number of packets (TCP segments + UDP) sent through
- * the network for this UID.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
+ * Return number of packets transmitted by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
*
- * @param uid The UID of the process to examine.
- * @return number of packets.
- * If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
*/
- public static native long getUidTxPackets(int uid);
+ public static long getUidTxPackets(int uid) {
+ return nativeGetUidStat(uid, TYPE_TX_PACKETS);
+ }
/**
- * Get the number of packets (TCP segments + UDP) received through
- * the network for this UID.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
+ * Return number of packets received by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
*
- * @param uid The UID of the process to examine.
- * @return number of packets
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
*/
- public static native long getUidRxPackets(int uid);
+ public static long getUidRxPackets(int uid) {
+ return nativeGetUidStat(uid, TYPE_RX_PACKETS);
+ }
/**
- * Get the number of TCP payload bytes sent for this UID.
- * This total does not include protocol and control overheads at
- * the transport and the lower layers of the networking stack.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxBytes(int)
*/
- public static native long getUidTcpTxBytes(int uid);
+ @Deprecated
+ public static long getUidTcpTxBytes(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of TCP payload bytes received for this UID.
- * This total does not include protocol and control overheads at
- * the transport and the lower layers of the networking stack.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxBytes(int)
*/
- public static native long getUidTcpRxBytes(int uid);
+ @Deprecated
+ public static long getUidTcpRxBytes(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of UDP payload bytes sent for this UID.
- * This total does not include protocol and control overheads at
- * the transport and the lower layers of the networking stack.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxBytes(int)
*/
- public static native long getUidUdpTxBytes(int uid);
+ @Deprecated
+ public static long getUidUdpTxBytes(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of UDP payload bytes received for this UID.
- * This total does not include protocol and control overheads at
- * the transport and the lower layers of the networking stack.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxBytes(int)
*/
- public static native long getUidUdpRxBytes(int uid);
+ @Deprecated
+ public static long getUidUdpRxBytes(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of TCP segments sent for this UID.
- * Does not include TCP control packets (SYN/ACKs/FIN/..).
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of TCP segments. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxPackets(int)
*/
- public static native long getUidTcpTxSegments(int uid);
+ @Deprecated
+ public static long getUidTcpTxSegments(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of TCP segments received for this UID.
- * Does not include TCP control packets (SYN/ACKs/FIN/..).
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of TCP segments. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxPackets(int)
*/
- public static native long getUidTcpRxSegments(int uid);
+ @Deprecated
+ public static long getUidTcpRxSegments(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of UDP packets sent for this UID.
- * Includes DNS requests.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of packets. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxPackets(int)
*/
- public static native long getUidUdpTxPackets(int uid);
+ @Deprecated
+ public static long getUidUdpTxPackets(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of UDP packets received for this UID.
- * Includes DNS responses.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of packets. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxPackets(int)
*/
- public static native long getUidUdpRxPackets(int uid);
+ @Deprecated
+ public static long getUidUdpRxPackets(int uid) {
+ return UNSUPPORTED;
+ }
/**
* Return detailed {@link NetworkStats} for the current UID. Requires no
* special permission.
*/
private static NetworkStats getDataLayerSnapshotForUid(Context context) {
+ // TODO: take snapshot locally, since proc file is now visible
final int uid = android.os.Process.myUid();
try {
return getStatsService().getDataLayerSnapshotForUid(uid);
@@ -587,7 +617,10 @@ public class TrafficStats {
private static final int TYPE_RX_PACKETS = 1;
private static final int TYPE_TX_BYTES = 2;
private static final int TYPE_TX_PACKETS = 3;
+ private static final int TYPE_TCP_RX_PACKETS = 4;
+ private static final int TYPE_TCP_TX_PACKETS = 5;
private static native long nativeGetTotalStat(int type);
private static native long nativeGetIfaceStat(String iface, int type);
+ private static native long nativeGetUidStat(int uid, int type);
}
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index cc6903d..4b022d9 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -20,6 +20,7 @@ import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Environment.UserEnvironment;
+import android.os.StrictMode;
import android.util.Log;
import java.io.File;
import java.io.IOException;
@@ -2326,4 +2327,16 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
return this;
}
}
+
+ /**
+ * If this is a {@code file://} Uri, it will be reported to
+ * {@link StrictMode}.
+ *
+ * @hide
+ */
+ public void checkFileUriExposed(String location) {
+ if ("file".equals(getScheme())) {
+ StrictMode.onFileUriExposed(location);
+ }
+ }
}
diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java
index fabe018..04f3974 100644
--- a/core/java/android/net/http/AndroidHttpClient.java
+++ b/core/java/android/net/http/AndroidHttpClient.java
@@ -17,6 +17,7 @@
package android.net.http;
import com.android.internal.http.HttpDateTime;
+
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
@@ -25,18 +26,18 @@ import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
-import org.apache.http.entity.AbstractHttpEntity;
-import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.protocol.ClientContext;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.HttpClientParams;
+import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.RequestWrapper;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
@@ -44,26 +45,26 @@ import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
-import org.apache.http.protocol.BasicHttpContext;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-import java.net.URI;
-
-import android.content.Context;
import android.content.ContentResolver;
+import android.content.Context;
import android.net.SSLCertificateSocketFactory;
import android.net.SSLSessionCache;
import android.os.Looper;
import android.util.Base64;
import android.util.Log;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
/**
* Implementation of the Apache {@link DefaultHttpClient} that is configured with
* reasonable default settings and registered schemes for Android.
@@ -266,7 +267,7 @@ public final class AndroidHttpClient implements HttpClient {
return delegate.execute(target, request, context);
}
- public <T> T execute(HttpUriRequest request,
+ public <T> T execute(HttpUriRequest request,
ResponseHandler<? extends T> responseHandler)
throws IOException, ClientProtocolException {
return delegate.execute(request, responseHandler);
@@ -404,6 +405,11 @@ public final class AndroidHttpClient implements HttpClient {
builder.append("curl ");
+ // add in the method
+ builder.append("-X ");
+ builder.append(request.getMethod());
+ builder.append(" ");
+
for (Header header: request.getAllHeaders()) {
if (!logAuthToken
&& (header.getName().equals("Authorization") ||
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index 08ba728..9c3e405 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -306,10 +306,9 @@ public final class NsdManager {
switch (message.what) {
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
- mConnected.countDown();
break;
case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
- // Ignore
+ mConnected.countDown();
break;
case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
Log.e(TAG, "Channel lost");
diff --git a/core/java/android/util/Poolable.java b/core/java/android/nfc/BeamShareData.aidl
index 87e0529..a47e240 100644
--- a/core/java/android/util/Poolable.java
+++ b/core/java/android/nfc/BeamShareData.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-package android.util;
+package android.nfc;
-/**
- * @hide
- */
-public interface Poolable<T> {
- void setNextPoolable(T element);
- T getNextPoolable();
- boolean isPooled();
- void setPooled(boolean isPooled);
-}
+parcelable BeamShareData;
diff --git a/core/java/android/nfc/BeamShareData.java b/core/java/android/nfc/BeamShareData.java
new file mode 100644
index 0000000..c30ba14
--- /dev/null
+++ b/core/java/android/nfc/BeamShareData.java
@@ -0,0 +1,62 @@
+package android.nfc;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Class to IPC data to be shared over Android Beam.
+ * Allows bundling NdefMessage, Uris and flags in a single
+ * IPC call. This is important as we want to reduce the
+ * amount of IPC calls at "touch time".
+ * @hide
+ */
+public final class BeamShareData implements Parcelable {
+ public final NdefMessage ndefMessage;
+ public final Uri[] uris;
+ public final int flags;
+
+ public BeamShareData(NdefMessage msg, Uri[] uris, int flags) {
+ this.ndefMessage = msg;
+ this.uris = uris;
+ this.flags = flags;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ int urisLength = (uris != null) ? uris.length : 0;
+ dest.writeParcelable(ndefMessage, 0);
+ dest.writeInt(urisLength);
+ if (urisLength > 0) {
+ dest.writeTypedArray(uris, 0);
+ }
+ dest.writeInt(this.flags);
+ }
+
+ public static final Parcelable.Creator<BeamShareData> CREATOR =
+ new Parcelable.Creator<BeamShareData>() {
+ @Override
+ public BeamShareData createFromParcel(Parcel source) {
+ Uri[] uris = null;
+ NdefMessage msg = source.readParcelable(NdefMessage.class.getClassLoader());
+ int numUris = source.readInt();
+ if (numUris > 0) {
+ uris = new Uri[numUris];
+ source.readTypedArray(uris, Uri.CREATOR);
+ }
+ int flags = source.readInt();
+
+ return new BeamShareData(msg, uris, flags);
+ }
+
+ @Override
+ public BeamShareData[] newArray(int size) {
+ return new BeamShareData[size];
+ }
+ };
+}
diff --git a/core/java/android/nfc/INdefPushCallback.aidl b/core/java/android/nfc/INdefPushCallback.aidl
index 1c6d5d0..16771dc 100644
--- a/core/java/android/nfc/INdefPushCallback.aidl
+++ b/core/java/android/nfc/INdefPushCallback.aidl
@@ -16,15 +16,13 @@
package android.nfc;
-import android.nfc.NdefMessage;
-import android.net.Uri;
+import android.nfc.BeamShareData;
/**
* @hide
*/
interface INdefPushCallback
{
- NdefMessage createMessage();
- Uri[] getUris();
+ BeamShareData createBeamShareData();
void onNdefPushComplete();
}
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 53b41d5..10183c0 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -110,6 +110,7 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
NfcAdapter.CreateBeamUrisCallback uriCallback = null;
Uri[] uris = null;
+ int flags = 0;
public NfcActivityState(Activity activity) {
if (activity.getWindow().isDestroyed()) {
throw new IllegalStateException("activity is already destroyed");
@@ -197,7 +198,7 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
- requestNfcServiceCallback(true);
+ requestNfcServiceCallback();
}
}
@@ -211,32 +212,34 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
- requestNfcServiceCallback(true);
+ requestNfcServiceCallback();
}
}
- public void setNdefPushMessage(Activity activity, NdefMessage message) {
+ public void setNdefPushMessage(Activity activity, NdefMessage message, int flags) {
boolean isResumed;
synchronized (NfcActivityManager.this) {
NfcActivityState state = getActivityState(activity);
state.ndefMessage = message;
+ state.flags = flags;
isResumed = state.resumed;
}
if (isResumed) {
- requestNfcServiceCallback(true);
+ requestNfcServiceCallback();
}
}
public void setNdefPushMessageCallback(Activity activity,
- NfcAdapter.CreateNdefMessageCallback callback) {
+ NfcAdapter.CreateNdefMessageCallback callback, int flags) {
boolean isResumed;
synchronized (NfcActivityManager.this) {
NfcActivityState state = getActivityState(activity);
state.ndefMessageCallback = callback;
+ state.flags = flags;
isResumed = state.resumed;
}
if (isResumed) {
- requestNfcServiceCallback(true);
+ requestNfcServiceCallback();
}
}
@@ -249,18 +252,17 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
- requestNfcServiceCallback(true);
+ requestNfcServiceCallback();
}
}
/**
* Request or unrequest NFC service callbacks for NDEF push.
* Makes IPC call - do not hold lock.
- * TODO: Do not do IPC on every onPause/onResume
*/
- void requestNfcServiceCallback(boolean request) {
+ void requestNfcServiceCallback() {
try {
- NfcAdapter.sService.setNdefPushCallback(request ? this : null);
+ NfcAdapter.sService.setNdefPushCallback(this);
} catch (RemoteException e) {
mAdapter.attemptDeadServiceRecovery(e);
}
@@ -268,38 +270,29 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
/** Callback from NFC service, usually on binder thread */
@Override
- public NdefMessage createMessage() {
- NfcAdapter.CreateNdefMessageCallback callback;
+ public BeamShareData createBeamShareData() {
+ NfcAdapter.CreateNdefMessageCallback ndefCallback;
+ NfcAdapter.CreateBeamUrisCallback urisCallback;
NdefMessage message;
+ Uri[] uris;
+ int flags;
synchronized (NfcActivityManager.this) {
NfcActivityState state = findResumedActivityState();
if (state == null) return null;
- callback = state.ndefMessageCallback;
+ ndefCallback = state.ndefMessageCallback;
+ urisCallback = state.uriCallback;
message = state.ndefMessage;
+ uris = state.uris;
+ flags = state.flags;
}
- // Make callback without lock
- if (callback != null) {
- return callback.createNdefMessage(mDefaultEvent);
- } else {
- return message;
- }
- }
-
- /** Callback from NFC service, usually on binder thread */
- @Override
- public Uri[] getUris() {
- Uri[] uris;
- NfcAdapter.CreateBeamUrisCallback callback;
- synchronized (NfcActivityManager.this) {
- NfcActivityState state = findResumedActivityState();
- if (state == null) return null;
- uris = state.uris;
- callback = state.uriCallback;
+ // Make callbacks without lock
+ if (ndefCallback != null) {
+ message = ndefCallback.createNdefMessage(mDefaultEvent);
}
- if (callback != null) {
- uris = callback.createBeamUris(mDefaultEvent);
+ if (urisCallback != null) {
+ uris = urisCallback.createBeamUris(mDefaultEvent);
if (uris != null) {
for (Uri uri : uris) {
if (uri == null) {
@@ -315,10 +308,9 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
}
}
}
- return uris;
- } else {
- return uris;
}
+
+ return new BeamShareData(message, uris, flags);
}
/** Callback from NFC service, usually on binder thread */
@@ -355,7 +347,7 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
if (state == null) return;
state.resumed = true;
}
- requestNfcServiceCallback(true);
+ requestNfcServiceCallback();
}
/** Callback from Activity life-cycle, on main thread */
@@ -367,7 +359,6 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
if (state == null) return;
state.resumed = false;
}
- requestNfcServiceCallback(false);
}
/** Callback from Activity life-cycle, on main thread */
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 4baceed..ca4a7d6 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -174,34 +174,31 @@ public final class NfcAdapter {
* Broadcast Action: The state of the local NFC adapter has been
* changed.
* <p>For example, NFC has been turned on or off.
- * <p>Always contains the extra field {@link #EXTRA_STATE}
- * @hide
+ * <p>Always contains the extra field {@link #EXTRA_ADAPTER_STATE}
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_ADAPTER_STATE_CHANGED =
"android.nfc.action.ADAPTER_STATE_CHANGED";
/**
- * Used as an int extra field in {@link #ACTION_STATE_CHANGED}
+ * Used as an int extra field in {@link #ACTION_ADAPTER_STATE_CHANGED}
* intents to request the current power state. Possible values are:
* {@link #STATE_OFF},
* {@link #STATE_TURNING_ON},
* {@link #STATE_ON},
* {@link #STATE_TURNING_OFF},
- * @hide
*/
public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
- /** @hide */
public static final int STATE_OFF = 1;
- /** @hide */
public static final int STATE_TURNING_ON = 2;
- /** @hide */
public static final int STATE_ON = 3;
- /** @hide */
public static final int STATE_TURNING_OFF = 4;
/** @hide */
+ public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 0x1;
+
+ /** @hide */
public static final String ACTION_HANDOVER_TRANSFER_STARTED =
"android.nfc.action.HANDOVER_TRANSFER_STARTED";
@@ -802,12 +799,12 @@ public final class NfcAdapter {
if (activity == null) {
throw new NullPointerException("activity cannot be null");
}
- mNfcActivityManager.setNdefPushMessage(activity, message);
+ mNfcActivityManager.setNdefPushMessage(activity, message, 0);
for (Activity a : activities) {
if (a == null) {
throw new NullPointerException("activities cannot contain null");
}
- mNfcActivityManager.setNdefPushMessage(a, message);
+ mNfcActivityManager.setNdefPushMessage(a, message, 0);
}
} catch (IllegalStateException e) {
if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
@@ -822,6 +819,16 @@ public final class NfcAdapter {
}
/**
+ * @hide
+ */
+ public void setNdefPushMessage(NdefMessage message, Activity activity, int flags) {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setNdefPushMessage(activity, message, flags);
+ }
+
+ /**
* Set a callback that dynamically generates NDEF messages to send using Android Beam (TM).
*
* <p>This method may be called at any time before {@link Activity#onDestroy},
@@ -893,12 +900,12 @@ public final class NfcAdapter {
if (activity == null) {
throw new NullPointerException("activity cannot be null");
}
- mNfcActivityManager.setNdefPushMessageCallback(activity, callback);
+ mNfcActivityManager.setNdefPushMessageCallback(activity, callback, 0);
for (Activity a : activities) {
if (a == null) {
throw new NullPointerException("activities cannot contain null");
}
- mNfcActivityManager.setNdefPushMessageCallback(a, callback);
+ mNfcActivityManager.setNdefPushMessageCallback(a, callback, 0);
}
} catch (IllegalStateException e) {
if (targetSdkVersion < android.os.Build.VERSION_CODES.JELLY_BEAN) {
@@ -913,6 +920,17 @@ public final class NfcAdapter {
}
/**
+ * @hide
+ */
+ public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
+ int flags) {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setNdefPushMessageCallback(activity, callback, flags);
+ }
+
+ /**
* Set a callback on successful Android Beam (TM).
*
* <p>This method may be called at any time before {@link Activity#onDestroy},
@@ -1101,7 +1119,7 @@ public final class NfcAdapter {
throw new NullPointerException();
}
enforceResumed(activity);
- mNfcActivityManager.setNdefPushMessage(activity, message);
+ mNfcActivityManager.setNdefPushMessage(activity, message, 0);
}
/**
@@ -1129,8 +1147,8 @@ public final class NfcAdapter {
throw new NullPointerException();
}
enforceResumed(activity);
- mNfcActivityManager.setNdefPushMessage(activity, null);
- mNfcActivityManager.setNdefPushMessageCallback(activity, null);
+ mNfcActivityManager.setNdefPushMessage(activity, null, 0);
+ mNfcActivityManager.setNdefPushMessageCallback(activity, null, 0);
mNfcActivityManager.setOnNdefPushCompleteCallback(activity, null);
}
diff --git a/core/java/android/nfc/tech/NfcBarcode.java b/core/java/android/nfc/tech/NfcBarcode.java
index 3149857..76627de 100644
--- a/core/java/android/nfc/tech/NfcBarcode.java
+++ b/core/java/android/nfc/tech/NfcBarcode.java
@@ -86,6 +86,28 @@ public final class NfcBarcode extends BasicTagTechnology {
/**
* Returns the barcode of an NfcBarcode tag.
*
+ * <p> Tags of {@link #TYPE_KOVIO} return 16 bytes:
+ * <ul>
+ * <p> The first byte is 0x80 ORd with a manufacturer ID, corresponding
+ * to ISO/IEC 7816-6.
+ * <p> The second byte describes the payload data format. Defined data
+ * format types include the following:<ul>
+ * <li>0x00: Reserved for manufacturer assignment</li>
+ * <li>0x01: 96-bit URL with "http://www." prefix</li>
+ * <li>0x02: 96-bit URL with "https://www." prefix</li>
+ * <li>0x03: 96-bit URL with "http://" prefix</li>
+ * <li>0x04: 96-bit URL with "https://" prefix</li>
+ * <li>0x05: 96-bit GS1 EPC</li>
+ * <li>0x06-0xFF: reserved</li>
+ * </ul>
+ * <p>The following 12 bytes are payload:<ul>
+ * <li> In case of a URL payload, the payload is encoded in US-ASCII,
+ * following the limitations defined in RF3987,
+ * {@see http://www.ietf.org/rfc/rfc3987.txt}</li>
+ * <li> In case of GS1 EPC daya, {@see http://www.gs1.org/gsmp/kc/epcglobal/tds/}
+ * for more details.</li></ul>
+ * <p>The last 2 bytes comprise the CRC.
+ * </ul>
* <p>Does not cause any RF activity and does not block.
*
* @return a byte array containing the barcode
diff --git a/core/java/android/nfc/tech/TagTechnology.java b/core/java/android/nfc/tech/TagTechnology.java
index 3493ea7..0e2c7c1 100644
--- a/core/java/android/nfc/tech/TagTechnology.java
+++ b/core/java/android/nfc/tech/TagTechnology.java
@@ -50,6 +50,7 @@ import java.io.IOException;
* <ul>
* <li>{@link MifareClassic}
* <li>{@link MifareUltralight}
+ * <li>{@link NfcBarcode}
* <li>{@link NdefFormatable} must only be enumerated on tags for which this Android device
* is capable of formatting. Proprietary knowledge is often required to format a tag
* to make it NDEF compatible.
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9821824..499ec77 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -93,6 +93,11 @@ public abstract class BatteryStats implements Parcelable {
public static final int VIDEO_TURNED_ON = 8;
/**
+ * A constant indicating a vibrator on timer
+ */
+ public static final int VIBRATOR_ON = 9;
+
+ /**
* Include all of the data in the stats, including previously saved data.
*/
public static final int STATS_SINCE_CHARGED = 0;
@@ -131,6 +136,7 @@ public abstract class BatteryStats implements Parcelable {
private static final String APK_DATA = "apk";
private static final String PROCESS_DATA = "pr";
private static final String SENSOR_DATA = "sr";
+ private static final String VIBRATOR_DATA = "vib";
private static final String WAKELOCK_DATA = "wl";
private static final String KERNEL_WAKELOCK_DATA = "kwl";
private static final String NETWORK_DATA = "nt";
@@ -277,6 +283,7 @@ public abstract class BatteryStats implements Parcelable {
int which);
public abstract long getAudioTurnedOnTime(long batteryRealtime, int which);
public abstract long getVideoTurnedOnTime(long batteryRealtime, int which);
+ public abstract Timer getVibratorOnTimer();
/**
* Note that these must match the constants in android.os.PowerManager.
@@ -294,6 +301,11 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getUserActivityCount(int type, int which);
public static abstract class Sensor {
+ /*
+ * FIXME: it's not correct to use this magic value because it
+ * could clash with a sensor handle (which are defined by
+ * the sensor HAL, and therefore out of our control
+ */
// Magic sensor number for the GPS.
public static final int GPS = -10000;
@@ -1395,6 +1407,16 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ Timer vibTimer = u.getVibratorOnTimer();
+ if (vibTimer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ long totalTime = (vibTimer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000;
+ int count = vibTimer.getCountLocked(which);
+ if (totalTime != 0) {
+ dumpLine(pw, uid, category, VIBRATOR_DATA, totalTime, count);
+ }
+ }
+
Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
if (processStats.size() > 0) {
for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
@@ -1919,6 +1941,26 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ Timer vibTimer = u.getVibratorOnTimer();
+ if (vibTimer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ long totalTime = (vibTimer.getTotalTimeLocked(
+ batteryRealtime, which) + 500) / 1000;
+ int count = vibTimer.getCountLocked(which);
+ //timer.logState();
+ if (totalTime != 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Vibrator: ");
+ formatTimeMs(sb, totalTime);
+ sb.append("realtime (");
+ sb.append(count);
+ sb.append(" times)");
+ pw.println(sb.toString());
+ uidActivity = true;
+ }
+ }
+
Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
if (processStats.size() > 0) {
for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 16b4835..7ffd30b 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -49,6 +49,11 @@ public class Binder implements IBinder {
private static final boolean FIND_POTENTIAL_LEAKS = false;
private static final String TAG = "Binder";
+ /**
+ * Control whether dump() calls are allowed.
+ */
+ private static String sDumpDisabled = null;
+
/* mObject is used by native code, do not remove or rename */
private int mObject;
private IInterface mOwner;
@@ -152,7 +157,15 @@ public class Binder implements IBinder {
* not return until the current process is exiting.
*/
public static final native void joinThreadPool();
-
+
+ /**
+ * Returns true if the specified interface is a proxy.
+ * @hide
+ */
+ public static final boolean isProxy(IInterface iface) {
+ return iface.asBinder() != iface;
+ }
+
/**
* Default constructor initializes the object.
*/
@@ -216,7 +229,23 @@ public class Binder implements IBinder {
}
return null;
}
-
+
+ /**
+ * Control disabling of dump calls in this process. This is used by the system
+ * process watchdog to disable incoming dump calls while it has detecting the system
+ * is hung and is reporting that back to the activity controller. This is to
+ * prevent the controller from getting hung up on bug reports at this point.
+ * @hide
+ *
+ * @param msg The message to show instead of the dump; if null, dumps are
+ * re-enabled.
+ */
+ public static void setDumpDisabled(String msg) {
+ synchronized (Binder.class) {
+ sDumpDisabled = msg;
+ }
+ }
+
/**
* Default implementation is a stub that returns false. You will want
* to override this to do the appropriate unmarshalling of transactions.
@@ -261,7 +290,15 @@ public class Binder implements IBinder {
FileOutputStream fout = new FileOutputStream(fd);
PrintWriter pw = new PrintWriter(fout);
try {
- dump(fd, pw, args);
+ final String disabled;
+ synchronized (Binder.class) {
+ disabled = sDumpDisabled;
+ }
+ if (disabled == null) {
+ dump(fd, pw, args);
+ } else {
+ pw.println(sDumpDisabled);
+ }
} finally {
pw.flush();
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a7f39d5..6c9f2d1 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -431,6 +431,11 @@ public class Build {
* </ul>
*/
public static final int JELLY_BEAN_MR1 = 17;
+
+ /**
+ * Android 4.3: Jelly Bean MR2, the revenge of the beans.
+ */
+ public static final int JELLY_BEAN_MR2 = 18;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 460a5fe..b8769b4 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -218,7 +218,7 @@ public final class Bundle implements Parcelable, Cloneable {
return;
}
if (mMap == null) {
- mMap = new HashMap<String, Object>();
+ mMap = new HashMap<String, Object>(N);
}
mParcelledData.readMapInternal(mMap, N, mClassLoader);
mParcelledData.recycle();
@@ -742,6 +742,25 @@ public final class Bundle implements Parcelable, Cloneable {
}
/**
+ * Inserts an {@link IBinder} value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * <p class="note">You should be very careful when using this function. In many
+ * places where Bundles are used (such as inside of Intent objects), the Bundle
+ * can live longer inside of another process than the process that had originally
+ * created it. In that case, the IBinder you supply here will become invalid
+ * when your process goes away, and no longer usable, even if a new process is
+ * created for you later on.</p>
+ *
+ * @param key a String, or null
+ * @param value an IBinder object, or null
+ */
+ public void putBinder(String key, IBinder value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
* Inserts an IBinder value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
@@ -749,7 +768,7 @@ public final class Bundle implements Parcelable, Cloneable {
* @param value an IBinder object, or null
*
* @deprecated
- * @hide
+ * @hide This is the old name of the function.
*/
@Deprecated
public void putIBinder(String key, IBinder value) {
@@ -1061,10 +1080,7 @@ public final class Bundle implements Parcelable, Cloneable {
*/
public String getString(String key) {
unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
+ final Object o = mMap.get(key);
try {
return (String) o;
} catch (ClassCastException e) {
@@ -1079,20 +1095,12 @@ public final class Bundle implements Parcelable, Cloneable {
*
* @param key a String, or null
* @param defaultValue Value to return if key does not exist
- * @return a String value, or null
+ * @return the String value associated with the given key, or defaultValue
+ * if no valid String object is currently mapped to that key.
*/
public String getString(String key, String defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (String) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "String", e);
- return defaultValue;
- }
+ final String s = getString(key);
+ return (s == null) ? defaultValue : s;
}
/**
@@ -1105,10 +1113,7 @@ public final class Bundle implements Parcelable, Cloneable {
*/
public CharSequence getCharSequence(String key) {
unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
+ final Object o = mMap.get(key);
try {
return (CharSequence) o;
} catch (ClassCastException e) {
@@ -1123,20 +1128,12 @@ public final class Bundle implements Parcelable, Cloneable {
*
* @param key a String, or null
* @param defaultValue Value to return if key does not exist
- * @return a CharSequence value, or null
+ * @return the CharSequence value associated with the given key, or defaultValue
+ * if no valid CharSequence object is currently mapped to that key.
*/
public CharSequence getCharSequence(String key, CharSequence defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (CharSequence) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "CharSequence", e);
- return defaultValue;
- }
+ final CharSequence cs = getCharSequence(key);
+ return (cs == null) ? defaultValue : cs;
}
/**
@@ -1565,9 +1562,31 @@ public final class Bundle implements Parcelable, Cloneable {
*
* @param key a String, or null
* @return an IBinder value, or null
+ */
+ public IBinder getBinder(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (IBinder) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "IBinder", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return an IBinder value, or null
*
* @deprecated
- * @hide
+ * @hide This is the old name of the function.
*/
@Deprecated
public IBinder getIBinder(String key) {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 2e77237..fd01da9 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -577,6 +577,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
*
* @see #startAllocCounting()
*/
+ @Deprecated
public static void stopAllocCounting() {
VMDebug.stopAllocCounting();
}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 1bada67..61eef1f 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -25,6 +25,7 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.io.File;
+import java.io.IOException;
/**
* Provides access to environment variables.
@@ -36,16 +37,21 @@ public class Environment {
private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE";
private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE";
+ private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
/** {@hide} */
public static String DIRECTORY_ANDROID = "Android";
- private static final File ROOT_DIRECTORY
- = getDirectory("ANDROID_ROOT", "/system");
+ private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
+ private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media");
+
+ private static final String CANONCIAL_EMULATED_STORAGE_TARGET = getCanonicalPathOrNull(
+ ENV_EMULATED_STORAGE_TARGET);
private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
private static UserEnvironment sCurrentUser;
+ private static boolean sUserRequired;
private static final Object sLock = new Object();
@@ -178,7 +184,7 @@ public class Environment {
* Gets the Android root directory.
*/
public static File getRootDirectory() {
- return ROOT_DIRECTORY;
+ return DIR_ANDROID_ROOT;
}
/**
@@ -218,7 +224,7 @@ public class Environment {
* @hide
*/
public static File getMediaStorageDirectory() {
- throwIfSystem();
+ throwIfUserRequired();
return sCurrentUser.getMediaStorageDirectory();
}
@@ -313,7 +319,7 @@ public class Environment {
* @see #isExternalStorageRemovable()
*/
public static File getExternalStorageDirectory() {
- throwIfSystem();
+ throwIfUserRequired();
return sCurrentUser.getExternalStorageDirectory();
}
@@ -460,7 +466,7 @@ public class Environment {
* using it such as with {@link File#mkdirs File.mkdirs()}.
*/
public static File getExternalStoragePublicDirectory(String type) {
- throwIfSystem();
+ throwIfUserRequired();
return sCurrentUser.getExternalStoragePublicDirectory(type);
}
@@ -469,7 +475,7 @@ public class Environment {
* @hide
*/
public static File getExternalStorageAndroidDataDir() {
- throwIfSystem();
+ throwIfUserRequired();
return sCurrentUser.getExternalStorageAndroidDataDir();
}
@@ -478,7 +484,7 @@ public class Environment {
* @hide
*/
public static File getExternalStorageAppDataDirectory(String packageName) {
- throwIfSystem();
+ throwIfUserRequired();
return sCurrentUser.getExternalStorageAppDataDirectory(packageName);
}
@@ -487,7 +493,7 @@ public class Environment {
* @hide
*/
public static File getExternalStorageAppMediaDirectory(String packageName) {
- throwIfSystem();
+ throwIfUserRequired();
return sCurrentUser.getExternalStorageAppMediaDirectory(packageName);
}
@@ -496,7 +502,7 @@ public class Environment {
* @hide
*/
public static File getExternalStorageAppObbDirectory(String packageName) {
- throwIfSystem();
+ throwIfUserRequired();
return sCurrentUser.getExternalStorageAppObbDirectory(packageName);
}
@@ -505,7 +511,7 @@ public class Environment {
* @hide
*/
public static File getExternalStorageAppFilesDirectory(String packageName) {
- throwIfSystem();
+ throwIfUserRequired();
return sCurrentUser.getExternalStorageAppFilesDirectory(packageName);
}
@@ -514,7 +520,7 @@ public class Environment {
* @hide
*/
public static File getExternalStorageAppCacheDirectory(String packageName) {
- throwIfSystem();
+ throwIfUserRequired();
return sCurrentUser.getExternalStorageAppCacheDirectory(packageName);
}
@@ -632,9 +638,28 @@ public class Environment {
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 String getCanonicalPathOrNull(String variableName) {
+ String path = System.getenv(variableName);
+ if (path == null) {
+ return null;
+ }
+ try {
+ return new File(path).getCanonicalPath();
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to resolve canonical path for " + path);
+ return null;
+ }
+ }
+
+ /** {@hide} */
+ public static void setUserRequired(boolean userRequired) {
+ sUserRequired = userRequired;
+ }
+
+ private static void throwIfUserRequired() {
+ if (sUserRequired) {
+ Log.wtf(TAG, "Path requests must specify a user by using UserEnvironment",
+ new Throwable());
}
}
@@ -649,4 +674,40 @@ public class Environment {
}
return cur;
}
+
+ /**
+ * If the given path exists on emulated external storage, return the
+ * translated backing path hosted on internal storage. This bypasses any
+ * emulation later, improving performance. This is <em>only</em> suitable
+ * for read-only access.
+ * <p>
+ * Returns original path if given path doesn't meet these criteria. Callers
+ * must hold {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}
+ * permission.
+ *
+ * @hide
+ */
+ public static File maybeTranslateEmulatedPathToInternal(File path) {
+ // Fast return if not emulated, or missing variables
+ if (!Environment.isExternalStorageEmulated()
+ || CANONCIAL_EMULATED_STORAGE_TARGET == null) {
+ return path;
+ }
+
+ try {
+ final String rawPath = path.getCanonicalPath();
+ if (rawPath.startsWith(CANONCIAL_EMULATED_STORAGE_TARGET)) {
+ final File internalPath = new File(DIR_MEDIA_STORAGE,
+ rawPath.substring(CANONCIAL_EMULATED_STORAGE_TARGET.length()));
+ if (internalPath.exists()) {
+ return internalPath;
+ }
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to resolve canonical path for " + path);
+ }
+
+ // Unable to translate to internal path; use original
+ return path;
+ }
}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 2bec1c1..9666d9a 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -16,6 +16,8 @@
package android.os;
+import android.util.Log;
+
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -25,6 +27,8 @@ import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.regex.Pattern;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;
@@ -34,6 +38,8 @@ import java.util.zip.CheckedInputStream;
* @hide
*/
public class FileUtils {
+ private static final String TAG = "FileUtils";
+
public static final int S_IRWXU = 00700;
public static final int S_IRUSR = 00400;
public static final int S_IWUSR = 00200;
@@ -161,7 +167,8 @@ public class FileUtils {
} else if (max < 0) { // "tail" mode: keep the last N
int len;
boolean rolled = false;
- byte[] last = null, data = null;
+ byte[] last = null;
+ byte[] data = null;
do {
if (last != null) rolled = true;
byte[] tmp = last; last = data; data = tmp;
@@ -237,4 +244,40 @@ public class FileUtils {
}
}
}
+
+ /**
+ * Delete older files in a directory until only those matching the given
+ * constraints remain.
+ *
+ * @param minCount Always keep at least this many files.
+ * @param minAge Always keep files younger than this age.
+ */
+ public static void deleteOlderFiles(File dir, int minCount, long minAge) {
+ if (minCount < 0 || minAge < 0) {
+ throw new IllegalArgumentException("Constraints must be positive or 0");
+ }
+
+ final File[] files = dir.listFiles();
+ if (files == null) return;
+
+ // Sort with newest files first
+ Arrays.sort(files, new Comparator<File>() {
+ @Override
+ public int compare(File lhs, File rhs) {
+ return (int) (rhs.lastModified() - lhs.lastModified());
+ }
+ });
+
+ // Keep at least minCount files
+ for (int i = minCount; i < files.length; i++) {
+ final File file = files[i];
+
+ // Keep files newer than minAge
+ final long age = System.currentTimeMillis() - file.lastModified();
+ if (age > minAge) {
+ Log.d(TAG, "Deleting old file " + file);
+ file.delete();
+ }
+ }
+ }
}
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 94de448..14d8f07 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -413,27 +413,32 @@ public class Handler {
/**
* Runs the specified task synchronously.
- *
+ * <p>
* 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.
- *
+ * </p><p>
* 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.
- *
+ * </p><p>
* 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.
- *
+ * </p><p>
* 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.
- *
+ * </p><p>
* 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.
+ * </p><p>
+ * When using this method, be sure to use {@link Looper#quitSafely} when
+ * quitting the looper. Otherwise {@link #runWithScissors} may hang indefinitely.
+ * (TODO: We should fix this by making MessageQueue aware of blocking runnables.)
+ * </p>
*
* @param r The Runnable that will be executed synchronously.
* @param timeout The timeout in milliseconds, or 0 to wait indefinitely.
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index daf1f59..2904105 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -48,6 +48,7 @@ public class HandlerThread extends Thread {
protected void onLooperPrepared() {
}
+ @Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
@@ -83,12 +84,25 @@ public class HandlerThread extends Thread {
}
return mLooper;
}
-
+
/**
- * Ask the currently running looper to quit. If the thread has not
- * been started or has finished (that is if {@link #getLooper} returns
- * null), then false is returned. Otherwise the looper is asked to
- * quit and true is returned.
+ * Quits the handler thread's looper.
+ * <p>
+ * Causes the handler thread's looper to terminate without processing any
+ * more messages in the message queue.
+ * </p><p>
+ * Any attempt to post messages to the queue after the looper is asked to quit will fail.
+ * For example, the {@link Handler#sendMessage(Message)} method will return false.
+ * </p><p class="note">
+ * Using this method may be unsafe because some messages may not be delivered
+ * before the looper terminates. Consider using {@link #quitSafely} instead to ensure
+ * that all pending work is completed in an orderly manner.
+ * </p>
+ *
+ * @return True if the looper looper has been asked to quit or false if the
+ * thread had not yet started running.
+ *
+ * @see #quitSafely
*/
public boolean quit() {
Looper looper = getLooper();
@@ -98,7 +112,34 @@ public class HandlerThread extends Thread {
}
return false;
}
-
+
+ /**
+ * Quits the handler thread's looper safely.
+ * <p>
+ * Causes the handler thread's looper to terminate as soon as all remaining messages
+ * in the message queue that are already due to be delivered have been handled.
+ * Pending delayed messages with due times in the future will not be delivered.
+ * </p><p>
+ * Any attempt to post messages to the queue after the looper is asked to quit will fail.
+ * For example, the {@link Handler#sendMessage(Message)} method will return false.
+ * </p><p>
+ * If the thread has not been started or has finished (that is if
+ * {@link #getLooper} returns null), then false is returned.
+ * Otherwise the looper is asked to quit and true is returned.
+ * </p>
+ *
+ * @return True if the looper looper has been asked to quit or false if the
+ * thread had not yet started running.
+ */
+ public boolean quitSafely() {
+ Looper looper = getLooper();
+ if (looper != null) {
+ looper.quitSafely();
+ return true;
+ }
+ return false;
+ }
+
/**
* Returns the identifier of this thread. See Process.myTid().
*/
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 2179fa1..45524c8 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -152,16 +152,6 @@ 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);
@@ -306,23 +296,6 @@ interface INetworkManagementService
boolean isBandwidthControlEnabled();
/**
- * Configures bandwidth throttling on an interface.
- */
- void setInterfaceThrottle(String iface, int rxKbps, int txKbps);
-
- /**
- * Returns the currently configured RX throttle values
- * for the specified interface
- */
- int getInterfaceRxThrottle(String iface);
-
- /**
- * Returns the currently configured TX throttle values
- * for the specified interface
- */
- int getInterfaceTxThrottle(String iface);
-
- /**
* Sets idletimer for an interface.
*
* This either initializes a new idletimer or increases its
@@ -351,7 +324,7 @@ interface INetworkManagementService
/**
* Bind name servers to an interface in the DNS resolver.
*/
- void setDnsServersForInterface(String iface, in String[] servers);
+ void setDnsServersForInterface(String iface, in String[] servers, String domains);
/**
* Flush the DNS cache associated with the default interface.
@@ -369,4 +342,29 @@ interface INetworkManagementService
void setFirewallEgressSourceRule(String addr, boolean allow);
void setFirewallEgressDestRule(String addr, int port, boolean allow);
void setFirewallUidRule(int uid, boolean allow);
+
+ /**
+ * Set a process (pid) to use the name servers associated with the specified interface.
+ */
+ void setDnsInterfaceForPid(String iface, int pid);
+
+ /**
+ * Clear a process (pid) from being associated with an interface.
+ */
+ void clearDnsInterfaceForPid(int pid);
+
+ /**
+ * Start the clatd (464xlat) service
+ */
+ void startClatd(String interfaceName);
+
+ /**
+ * Stop the clatd (464xlat) service
+ */
+ void stopClatd();
+
+ /**
+ * Determine whether the clatd (464xlat) service has been started
+ */
+ boolean isClatdStarted();
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index ec02ae0..a11358a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -17,8 +17,10 @@
package android.os;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.content.pm.UserInfo;
+import android.content.RestrictionEntry;
import android.graphics.Bitmap;
/**
@@ -32,9 +34,16 @@ interface IUserManager {
Bitmap getUserIcon(int userHandle);
List<UserInfo> getUsers(boolean excludeDying);
UserInfo getUserInfo(int userHandle);
+ boolean isRestricted();
void setGuestEnabled(boolean enable);
boolean isGuestEnabled();
void wipeUser(int userHandle);
int getUserSerialNumber(int userHandle);
int getUserHandle(int userSerialNumber);
+ Bundle getUserRestrictions(int userHandle);
+ void setUserRestrictions(in Bundle restrictions, int userHandle);
+ void setApplicationRestrictions(in String packageName, in Bundle restrictions,
+ int userHandle);
+ Bundle getApplicationRestrictions(in String packageName);
+ Bundle getApplicationRestrictionsForUser(in String packageName, int userHandle);
}
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 2c2fe8a..456ffb1 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -20,8 +20,8 @@ package android.os;
interface IVibratorService
{
boolean hasVibrator();
- void vibrate(long milliseconds, IBinder token);
- void vibratePattern(in long[] pattern, int repeat, IBinder token);
+ void vibrate(int uid, String packageName, long milliseconds, IBinder token);
+ void vibratePattern(int uid, String packageName, in long[] pattern, int repeat, IBinder token);
void cancelVibrate(IBinder token);
}
diff --git a/core/java/android/os/LatencyTimer.java b/core/java/android/os/LatencyTimer.java
deleted file mode 100644
index ed2f0f9e..0000000
--- a/core/java/android/os/LatencyTimer.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.util.Log;
-
-import java.util.HashMap;
-
-/**
- * A class to help with measuring latency in your code.
- *
- * Suggested usage:
- * 1) Instanciate a LatencyTimer as a class field.
- * private [static] LatencyTimer mLt = new LatencyTimer(100, 1000);
- * 2) At various points in the code call sample with a string and the time delta to some fixed time.
- * The string should be unique at each point of the code you are measuring.
- * mLt.sample("before processing event", System.nanoTime() - event.getEventTimeNano());
- * processEvent(event);
- * mLt.sample("after processing event ", System.nanoTime() - event.getEventTimeNano());
- *
- * @hide
- */
-public final class LatencyTimer
-{
- final String TAG = "LatencyTimer";
- final int mSampleSize;
- final int mScaleFactor;
- volatile HashMap<String, long[]> store = new HashMap<String, long[]>();
-
- /**
- * Creates a LatencyTimer object
- * @param sampleSize number of samples to collect before printing out the average
- * @param scaleFactor divisor used to make each sample smaller to prevent overflow when
- * (sampleSize * average sample value)/scaleFactor > Long.MAX_VALUE
- */
- public LatencyTimer(int sampleSize, int scaleFactor) {
- if (scaleFactor == 0) {
- scaleFactor = 1;
- }
- mScaleFactor = scaleFactor;
- mSampleSize = sampleSize;
- }
-
- /**
- * Add a sample delay for averaging.
- * @param tag string used for printing out the result. This should be unique at each point of
- * this called.
- * @param delta time difference from an unique point of reference for a particular iteration
- */
- public void sample(String tag, long delta) {
- long[] array = getArray(tag);
-
- // array[mSampleSize] holds the number of used entries
- final int index = (int) array[mSampleSize]++;
- array[index] = delta;
- if (array[mSampleSize] == mSampleSize) {
- long totalDelta = 0;
- for (long d : array) {
- totalDelta += d/mScaleFactor;
- }
- array[mSampleSize] = 0;
- Log.i(TAG, tag + " average = " + totalDelta / mSampleSize);
- }
- }
-
- private long[] getArray(String tag) {
- long[] data = store.get(tag);
- if (data == null) {
- synchronized(store) {
- data = store.get(tag);
- if (data == null) {
- data = new long[mSampleSize + 1];
- store.put(tag, data);
- data[mSampleSize] = 0;
- }
- }
- }
- return data;
- }
-}
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 02135bc..d5cf771 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -50,7 +50,7 @@ import android.util.PrefixPrinter;
* }
* }</pre>
*/
-public class Looper {
+public final class Looper {
private static final String TAG = "Looper";
// sThreadLocal.get() will return null unless you've called prepare().
@@ -192,12 +192,47 @@ public class Looper {
}
/**
+ * Returns true if the current thread is this looper's thread.
+ * @hide
+ */
+ public boolean isCurrentThread() {
+ return Thread.currentThread() == mThread;
+ }
+
+ /**
* Quits the looper.
+ * <p>
+ * Causes the {@link #loop} method to terminate without processing any
+ * more messages in the message queue.
+ * </p><p>
+ * Any attempt to post messages to the queue after the looper is asked to quit will fail.
+ * For example, the {@link Handler#sendMessage(Message)} method will return false.
+ * </p><p class="note">
+ * Using this method may be unsafe because some messages may not be delivered
+ * before the looper terminates. Consider using {@link #quitSafely} instead to ensure
+ * that all pending work is completed in an orderly manner.
+ * </p>
*
- * Causes the {@link #loop} method to terminate as soon as possible.
+ * @see #quitSafely
*/
public void quit() {
- mQueue.quit();
+ mQueue.quit(false);
+ }
+
+ /**
+ * Quits the looper safely.
+ * <p>
+ * Causes the {@link #loop} method to terminate as soon as all remaining messages
+ * in the message queue that are already due to be delivered have been handled.
+ * However pending delayed messages with due times in the future will not be
+ * delivered before the loop terminates.
+ * </p><p>
+ * Any attempt to post messages to the queue after the looper is asked to quit will fail.
+ * For example, the {@link Handler#sendMessage(Message)} method will return false.
+ * </p>
+ */
+ public void quitSafely() {
+ mQueue.quit(true);
}
/**
@@ -223,7 +258,7 @@ public class Looper {
*
* @hide
*/
- public final int postSyncBarrier() {
+ public int postSyncBarrier() {
return mQueue.enqueueSyncBarrier(SystemClock.uptimeMillis());
}
@@ -238,7 +273,7 @@ public class Looper {
*
* @hide
*/
- public final void removeSyncBarrier(int token) {
+ public void removeSyncBarrier(int token) {
mQueue.removeSyncBarrier(token);
}
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 5ad60ec..bf7e5ca 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -29,7 +29,7 @@ import java.util.ArrayList;
* <p>You can retrieve the MessageQueue for the current thread with
* {@link Looper#myQueue() Looper.myQueue()}.
*/
-public class MessageQueue {
+public final class MessageQueue {
// True if the message queue can be quit.
private final boolean mQuitAllowed;
@@ -48,10 +48,10 @@ public class MessageQueue {
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
private int mNextBarrierToken;
- private native void nativeInit();
- private native void nativeDestroy();
- private native void nativePollOnce(int ptr, int timeoutMillis);
- private native void nativeWake(int ptr);
+ private native static int nativeInit();
+ private native static void nativeDestroy(int ptr);
+ private native static void nativePollOnce(int ptr, int timeoutMillis);
+ private native static void nativeWake(int ptr);
/**
* Callback interface for discovering when a thread is going to block
@@ -78,7 +78,7 @@ public class MessageQueue {
*
* @param handler The IdleHandler to be added.
*/
- public final void addIdleHandler(IdleHandler handler) {
+ public void addIdleHandler(IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
@@ -94,7 +94,7 @@ public class MessageQueue {
*
* @param handler The IdleHandler to be removed.
*/
- public final void removeIdleHandler(IdleHandler handler) {
+ public void removeIdleHandler(IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
@@ -102,19 +102,26 @@ public class MessageQueue {
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
- nativeInit();
+ mPtr = nativeInit();
}
@Override
protected void finalize() throws Throwable {
try {
- nativeDestroy();
+ dispose();
} finally {
super.finalize();
}
}
- final Message next() {
+ private void dispose() {
+ if (mPtr != 0) {
+ nativeDestroy(mPtr);
+ mPtr = 0;
+ }
+ }
+
+ Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
@@ -125,10 +132,6 @@ public class MessageQueue {
nativePollOnce(mPtr, nextPollTimeoutMillis);
synchronized (this) {
- if (mQuiting) {
- return null;
- }
-
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
@@ -162,6 +165,12 @@ public class MessageQueue {
nextPollTimeoutMillis = -1;
}
+ // Process the quit message now that all pending messages have been handled.
+ if (mQuiting) {
+ dispose();
+ return null;
+ }
+
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
@@ -210,7 +219,7 @@ public class MessageQueue {
}
}
- final void quit() {
+ void quit(boolean safe) {
if (!mQuitAllowed) {
throw new RuntimeException("Main thread not allowed to quit.");
}
@@ -220,11 +229,17 @@ public class MessageQueue {
return;
}
mQuiting = true;
+
+ if (safe) {
+ removeAllFutureMessagesLocked();
+ } else {
+ removeAllMessagesLocked();
+ }
}
nativeWake(mPtr);
}
- final int enqueueSyncBarrier(long when) {
+ int enqueueSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
@@ -251,7 +266,7 @@ public class MessageQueue {
}
}
- final void removeSyncBarrier(int token) {
+ void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
final boolean needWake;
@@ -280,7 +295,7 @@ public class MessageQueue {
}
}
- final boolean enqueueMessage(Message msg, long when) {
+ boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
@@ -330,7 +345,7 @@ public class MessageQueue {
return true;
}
- final boolean hasMessages(Handler h, int what, Object object) {
+ boolean hasMessages(Handler h, int what, Object object) {
if (h == null) {
return false;
}
@@ -347,7 +362,7 @@ public class MessageQueue {
}
}
- final boolean hasMessages(Handler h, Runnable r, Object object) {
+ boolean hasMessages(Handler h, Runnable r, Object object) {
if (h == null) {
return false;
}
@@ -364,7 +379,7 @@ public class MessageQueue {
}
}
- final void removeMessages(Handler h, int what, Object object) {
+ void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
}
@@ -398,7 +413,7 @@ public class MessageQueue {
}
}
- final void removeMessages(Handler h, Runnable r, Object object) {
+ void removeMessages(Handler h, Runnable r, Object object) {
if (h == null || r == null) {
return;
}
@@ -432,7 +447,7 @@ public class MessageQueue {
}
}
- final void removeCallbacksAndMessages(Handler h, Object object) {
+ void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
@@ -464,4 +479,42 @@ public class MessageQueue {
}
}
}
+
+ private void removeAllMessagesLocked() {
+ Message p = mMessages;
+ while (p != null) {
+ Message n = p.next;
+ p.recycle();
+ p = n;
+ }
+ mMessages = null;
+ }
+
+ private void removeAllFutureMessagesLocked() {
+ final long now = SystemClock.uptimeMillis();
+ Message p = mMessages;
+ if (p != null) {
+ if (p.when > now) {
+ removeAllMessagesLocked();
+ } else {
+ Message n;
+ for (;;) {
+ n = p.next;
+ if (n == null) {
+ return;
+ }
+ if (n.when > now) {
+ break;
+ }
+ p = n;
+ }
+ p.next = null;
+ do {
+ p = n;
+ n = p.next;
+ p.recycle();
+ } while (n != null);
+ }
+ }
+ }
}
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index 8de4e06..ac6027f 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -49,6 +49,22 @@ public class NullVibrator extends Vibrator {
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public void vibrate(int owningUid, String owningPackage, long milliseconds) {
+ vibrate(milliseconds);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
+ vibrate(pattern, repeat);
+ }
+
@Override
public void cancel() {
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 788ab74..0916ea9 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1254,6 +1254,12 @@ public final class Parcel {
p.writeToParcel(this, parcelableFlags);
}
+ /** @hide */
+ public final void writeParcelableCreator(Parcelable p) {
+ String name = p.getClass().getName();
+ writeString(name);
+ }
+
/**
* Write a generic serializable object in to a Parcel. It is strongly
* recommended that this method be avoided, since the serialization
@@ -2046,6 +2052,28 @@ public final class Parcel {
* was an error trying to instantiate the Parcelable.
*/
public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
+ Parcelable.Creator<T> creator = readParcelableCreator(loader);
+ if (creator == null) {
+ return null;
+ }
+ if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
+ return ((Parcelable.ClassLoaderCreator<T>)creator).createFromParcel(this, loader);
+ }
+ return creator.createFromParcel(this);
+ }
+
+ /** @hide */
+ public final <T extends Parcelable> T readCreator(Parcelable.Creator<T> creator,
+ ClassLoader loader) {
+ if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
+ return ((Parcelable.ClassLoaderCreator<T>)creator).createFromParcel(this, loader);
+ }
+ return creator.createFromParcel(this);
+ }
+
+ /** @hide */
+ public final <T extends Parcelable> Parcelable.Creator<T> readParcelableCreator(
+ ClassLoader loader) {
String name = readString();
if (name == null) {
return null;
@@ -2066,14 +2094,14 @@ public final class Parcel {
creator = (Parcelable.Creator)f.get(null);
}
catch (IllegalAccessException e) {
- Log.e(TAG, "Class not found when unmarshalling: "
- + name + ", e: " + e);
+ Log.e(TAG, "Illegal access when unmarshalling: "
+ + name, e);
throw new BadParcelableException(
"IllegalAccessException when unmarshalling: " + name);
}
catch (ClassNotFoundException e) {
Log.e(TAG, "Class not found when unmarshalling: "
- + name + ", e: " + e);
+ + name, e);
throw new BadParcelableException(
"ClassNotFoundException when unmarshalling: " + name);
}
@@ -2087,6 +2115,10 @@ public final class Parcel {
+ "Parcelable.Creator object called "
+ " CREATOR on class " + name);
}
+ catch (NullPointerException e) {
+ throw new BadParcelableException("Parcelable protocol requires "
+ + "the CREATOR object to be static on class " + name);
+ }
if (creator == null) {
throw new BadParcelableException("Parcelable protocol requires a "
+ "Parcelable.Creator object called "
@@ -2097,10 +2129,7 @@ public final class Parcel {
}
}
- if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
- return ((Parcelable.ClassLoaderCreator<T>)creator).createFromParcel(this, loader);
- }
- return creator.createFromParcel(this);
+ return creator;
}
/**
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index ec660ee..3de362c 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -108,13 +108,6 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
public static ParcelFileDescriptor open(File file, int mode)
throws FileNotFoundException {
String path = file.getPath();
- SecurityManager security = System.getSecurityManager();
- if (security != null) {
- security.checkRead(path);
- if ((mode&MODE_WRITE_ONLY) != 0) {
- security.checkWrite(path);
- }
- }
if ((mode&MODE_READ_WRITE) == 0) {
throw new IllegalArgumentException(
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 05099fb..476b4ea 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -166,11 +166,6 @@ public class Process {
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
* {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
@@ -806,7 +801,15 @@ public class Process {
*/
public static final native void setProcessGroup(int pid, int group)
throws IllegalArgumentException, SecurityException;
-
+
+ /**
+ * Return the scheduling group of requested process.
+ *
+ * @hide
+ */
+ public static final native int getProcessGroup(int pid)
+ throws IllegalArgumentException, SecurityException;
+
/**
* Set the priority of the calling thread, based on Linux priorities. See
* {@link #setThreadPriority(int, int)} for more information.
diff --git a/core/java/android/os/SchedulingPolicyService.java b/core/java/android/os/SchedulingPolicyService.java
deleted file mode 100644
index a3fede6..0000000
--- a/core/java/android/os/SchedulingPolicyService.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.os.Process;
-import android.util.Log;
-
-/**
- * The implementation of the scheduling policy service interface.
- *
- * @hide
- */
-public class SchedulingPolicyService extends ISchedulingPolicyService.Stub {
-
- private static final String TAG = "SchedulingPolicyService";
-
- // Minimum and maximum values allowed for requestPriority parameter prio
- private static final int PRIORITY_MIN = 1;
- private static final int PRIORITY_MAX = 3;
-
- public SchedulingPolicyService() {
- }
-
- public int requestPriority(int pid, int tid, int prio) {
- //Log.i(TAG, "requestPriority(pid=" + pid + ", tid=" + tid + ", prio=" + prio + ")");
-
- // Verify that caller is mediaserver, priority is in range, and that the
- // callback thread specified by app belongs to the app that called mediaserver.
- // Once we've verified that the caller is mediaserver, we can trust the pid but
- // we can't trust the tid. No need to explicitly check for pid == 0 || tid == 0,
- // since if not the case then the getThreadGroupLeader() test will also fail.
- if (Binder.getCallingUid() != Process.MEDIA_UID || prio < PRIORITY_MIN ||
- prio > PRIORITY_MAX || Process.getThreadGroupLeader(tid) != pid) {
- return PackageManager.PERMISSION_DENIED;
- }
- try {
- // make good use of our CAP_SYS_NICE capability
- Process.setThreadGroup(tid, Binder.getCallingPid() == pid ?
- Process.THREAD_GROUP_AUDIO_SYS : Process.THREAD_GROUP_AUDIO_APP);
- // must be in this order or it fails the schedulability constraint
- Process.setThreadScheduler(tid, Process.SCHED_FIFO, prio);
- } catch (RuntimeException e) {
- return PackageManager.PERMISSION_DENIED;
- }
- return PackageManager.PERMISSION_GRANTED;
- }
-
-}
diff --git a/core/java/android/os/StatFs.java b/core/java/android/os/StatFs.java
index ca7fdba..2314057 100644
--- a/core/java/android/os/StatFs.java
+++ b/core/java/android/os/StatFs.java
@@ -57,36 +57,92 @@ public class StatFs {
}
/**
- * The size, in bytes, of a block on the file system. This corresponds to
- * the Unix {@code statfs.f_bsize} field.
+ * @deprecated Use {@link #getBlockSizeLong()} instead.
*/
+ @Deprecated
public int getBlockSize() {
return (int) mStat.f_bsize;
}
/**
- * The total number of blocks on the file system. This corresponds to the
- * Unix {@code statfs.f_blocks} field.
+ * The size, in bytes, of a block on the file system. This corresponds to
+ * the Unix {@code statfs.f_bsize} field.
*/
+ public long getBlockSizeLong() {
+ return mStat.f_bsize;
+ }
+
+ /**
+ * @deprecated Use {@link #getBlockCountLong()} instead.
+ */
+ @Deprecated
public int getBlockCount() {
return (int) mStat.f_blocks;
}
/**
+ * The total number of blocks on the file system. This corresponds to the
+ * Unix {@code statfs.f_blocks} field.
+ */
+ public long getBlockCountLong() {
+ return mStat.f_blocks;
+ }
+
+ /**
+ * @deprecated Use {@link #getFreeBlocksLong()} instead.
+ */
+ @Deprecated
+ public int getFreeBlocks() {
+ return (int) mStat.f_bfree;
+ }
+
+ /**
* The total number of blocks that are free on the file system, including
* reserved blocks (that are not available to normal applications). This
* corresponds to the Unix {@code statfs.f_bfree} field. Most applications
* will want to use {@link #getAvailableBlocks()} instead.
*/
- public int getFreeBlocks() {
- return (int) mStat.f_bfree;
+ public long getFreeBlocksLong() {
+ return mStat.f_bfree;
}
/**
- * The number of blocks that are free on the file system and available to
- * applications. This corresponds to the Unix {@code statfs.f_bavail} field.
+ * The number of bytes that are free on the file system, including reserved
+ * blocks (that are not available to normal applications). Most applications
+ * will want to use {@link #getAvailableBytes()} instead.
*/
+ public long getFreeBytes() {
+ return mStat.f_bfree * mStat.f_bsize;
+ }
+
+ /**
+ * @deprecated Use {@link #getAvailableBlocksLong()} instead.
+ */
+ @Deprecated
public int getAvailableBlocks() {
return (int) mStat.f_bavail;
}
+
+ /**
+ * The number of blocks that are free on the file system and available to
+ * applications. This corresponds to the Unix {@code statfs.f_bavail} field.
+ */
+ public long getAvailableBlocksLong() {
+ return mStat.f_bavail;
+ }
+
+ /**
+ * The number of bytes that are free on the file system and available to
+ * applications.
+ */
+ public long getAvailableBytes() {
+ return mStat.f_bavail * mStat.f_bsize;
+ }
+
+ /**
+ * The total number of bytes supported by the file system.
+ */
+ public long getTotalBytes() {
+ return mStat.f_blocks * mStat.f_bsize;
+ }
}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index f682abe..3267939 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -203,10 +203,15 @@ public final class StrictMode {
*/
public static final int DETECT_VM_REGISTRATION_LEAKS = 0x2000; // for VmPolicy
+ /**
+ * @hide
+ */
+ private static final int DETECT_VM_FILE_URI_EXPOSURE = 0x4000; // for VmPolicy
+
private static final int ALL_VM_DETECT_BITS =
DETECT_VM_CURSOR_LEAKS | DETECT_VM_CLOSABLE_LEAKS |
DETECT_VM_ACTIVITY_LEAKS | DETECT_VM_INSTANCE_LEAKS |
- DETECT_VM_REGISTRATION_LEAKS;
+ DETECT_VM_REGISTRATION_LEAKS | DETECT_VM_FILE_URI_EXPOSURE;
/**
* @hide
@@ -628,7 +633,8 @@ public final class StrictMode {
*/
public Builder detectAll() {
return enable(DETECT_VM_ACTIVITY_LEAKS | DETECT_VM_CURSOR_LEAKS
- | DETECT_VM_CLOSABLE_LEAKS | DETECT_VM_REGISTRATION_LEAKS);
+ | DETECT_VM_CLOSABLE_LEAKS | DETECT_VM_REGISTRATION_LEAKS
+ | DETECT_VM_FILE_URI_EXPOSURE);
}
/**
@@ -666,6 +672,16 @@ public final class StrictMode {
}
/**
+ * Detect when a {@code file://} {@link android.net.Uri} is exposed beyond this
+ * app. The receiving app may not have access to the sent path.
+ * Instead, when sharing files between apps, {@code content://}
+ * should be used with permission grants.
+ */
+ public Builder detectFileUriExposure() {
+ return enable(DETECT_VM_FILE_URI_EXPOSURE);
+ }
+
+ /**
* Crashes the whole process on violation. This penalty runs at
* the end of all enabled penalties so yo you'll still get
* your logging or other violations before the process dies.
@@ -1524,6 +1540,13 @@ public final class StrictMode {
/**
* @hide
*/
+ public static boolean vmFileUriExposureEnabled() {
+ return (sVmPolicyMask & DETECT_VM_FILE_URI_EXPOSURE) != 0;
+ }
+
+ /**
+ * @hide
+ */
public static void onSqliteObjectLeaked(String message, Throwable originStack) {
onVmPolicyViolation(message, originStack);
}
@@ -1549,6 +1572,14 @@ public final class StrictMode {
onVmPolicyViolation(null, originStack);
}
+ /**
+ * @hide
+ */
+ public static void onFileUriExposed(String location) {
+ final String message = "file:// Uri exposed through " + location;
+ onVmPolicyViolation(message, new Throwable(message));
+ }
+
// Map from VM violation fingerprint to uptime millis.
private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<Integer, Long>();
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index c9adf45..729c64b 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -138,8 +138,6 @@ public final class SystemClock {
/**
* Returns milliseconds since boot, not counting time spent in deep sleep.
- * <b>Note:</b> This value may get reset occasionally (before it would
- * otherwise wrap around).
*
* @return milliseconds of non-sleep uptime since boot.
*/
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 7c5a47e..e66fb28 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -16,6 +16,8 @@
package android.os;
+import android.app.ActivityThread;
+import android.content.Context;
import android.util.Log;
/**
@@ -26,10 +28,18 @@ import android.util.Log;
public class SystemVibrator extends Vibrator {
private static final String TAG = "Vibrator";
+ private final String mPackageName;
private final IVibratorService mService;
private final Binder mToken = new Binder();
public SystemVibrator() {
+ mPackageName = ActivityThread.currentPackageName();
+ mService = IVibratorService.Stub.asInterface(
+ ServiceManager.getService("vibrator"));
+ }
+
+ public SystemVibrator(Context context) {
+ mPackageName = context.getBasePackageName();
mService = IVibratorService.Stub.asInterface(
ServiceManager.getService("vibrator"));
}
@@ -49,19 +59,35 @@ public class SystemVibrator extends Vibrator {
@Override
public void vibrate(long milliseconds) {
+ vibrate(Process.myUid(), mPackageName, milliseconds);
+ }
+
+ @Override
+ public void vibrate(long[] pattern, int repeat) {
+ vibrate(Process.myUid(), mPackageName, pattern, repeat);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void vibrate(int owningUid, String owningPackage, long milliseconds) {
if (mService == null) {
Log.w(TAG, "Failed to vibrate; no vibrator service.");
return;
}
try {
- mService.vibrate(milliseconds, mToken);
+ mService.vibrate(owningUid, owningPackage, milliseconds, mToken);
} catch (RemoteException e) {
Log.w(TAG, "Failed to vibrate.", e);
}
}
+ /**
+ * @hide
+ */
@Override
- public void vibrate(long[] pattern, int repeat) {
+ public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
if (mService == null) {
Log.w(TAG, "Failed to vibrate; no vibrator service.");
return;
@@ -71,7 +97,7 @@ public class SystemVibrator extends Vibrator {
// anyway
if (repeat < pattern.length) {
try {
- mService.vibratePattern(pattern, repeat, mToken);
+ mService.vibratePattern(owningUid, owningPackage, pattern, repeat, mToken);
} catch (RemoteException e) {
Log.w(TAG, "Failed to vibrate.", e);
}
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 0ca9183..e53cb5e 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -19,40 +19,57 @@ package android.os;
import android.util.Log;
/**
- * Writes trace events to the kernel trace buffer. These trace events can be
- * collected using the "atrace" program for offline analysis.
+ * Writes trace events to the system trace buffer. These trace events can be
+ * collected and visualized using the Systrace tool.
*
* This tracing mechanism is independent of the method tracing mechanism
* offered by {@link Debug#startMethodTracing}. In particular, it enables
- * tracing of events that occur across processes.
- *
- * @hide
+ * tracing of events that occur across multiple processes.
*/
public final class Trace {
+ /*
+ * Writes trace events to the kernel trace buffer. These trace events can be
+ * collected using the "atrace" program for offline analysis.
+ */
+
private static final String TAG = "Trace";
- // These tags must be kept in sync with frameworks/native/include/utils/Trace.h.
+ // These tags must be kept in sync with system/core/include/cutils/trace.h.
+ /** @hide */
public static final long TRACE_TAG_NEVER = 0;
+ /** @hide */
public static final long TRACE_TAG_ALWAYS = 1L << 0;
+ /** @hide */
public static final long TRACE_TAG_GRAPHICS = 1L << 1;
+ /** @hide */
public static final long TRACE_TAG_INPUT = 1L << 2;
+ /** @hide */
public static final long TRACE_TAG_VIEW = 1L << 3;
+ /** @hide */
public static final long TRACE_TAG_WEBVIEW = 1L << 4;
+ /** @hide */
public static final long TRACE_TAG_WINDOW_MANAGER = 1L << 5;
+ /** @hide */
public static final long TRACE_TAG_ACTIVITY_MANAGER = 1L << 6;
+ /** @hide */
public static final long TRACE_TAG_SYNC_MANAGER = 1L << 7;
+ /** @hide */
public static final long TRACE_TAG_AUDIO = 1L << 8;
+ /** @hide */
public static final long TRACE_TAG_VIDEO = 1L << 9;
+ /** @hide */
public static final long TRACE_TAG_CAMERA = 1L << 10;
- private static final long TRACE_TAG_NOT_READY = 1L << 63;
+ /** @hide */
+ public static final long TRACE_TAG_HAL = 1L << 11;
+ /** @hide */
+ public static final long TRACE_TAG_APP = 1L << 12;
+ /** @hide */
+ public static final long TRACE_TAG_RESOURCES = 1L << 13;
+ /** @hide */
+ public static final long TRACE_TAG_DALVIK = 1L << 14;
- 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", "Camera",
- };
-
- public static final String PROPERTY_TRACE_TAG_ENABLEFLAGS = "debug.atrace.tags.enableflags";
+ private static final long TRACE_TAG_NOT_READY = 1L << 63;
+ private static final int MAX_SECTION_NAME_LEN = 127;
// Must be volatile to avoid word tearing.
private static volatile long sEnabledTags = TRACE_TAG_NOT_READY;
@@ -61,6 +78,10 @@ public final class Trace {
private static native void nativeTraceCounter(long tag, String name, int value);
private static native void nativeTraceBegin(long tag, String name);
private static native void nativeTraceEnd(long tag);
+ private static native void nativeAsyncTraceBegin(long tag, String name, int cookie);
+ private static native void nativeAsyncTraceEnd(long tag, String name, int cookie);
+ private static native void nativeSetAppTracingAllowed(boolean allowed);
+ private static native void nativeSetTracingEnabled(boolean allowed);
static {
// We configure two separate change callbacks, one in Trace.cpp and one here. The
@@ -97,10 +118,6 @@ public final class Trace {
*/
private static long cacheEnabledTags() {
long tags = nativeGetEnabledTags();
- if (tags == TRACE_TAG_NOT_READY) {
- Log.w(TAG, "Unexpected value from nativeGetEnabledTags: " + tags);
- // keep going
- }
sEnabledTags = tags;
return tags;
}
@@ -110,6 +127,8 @@ public final class Trace {
*
* @param traceTag The trace tag to check.
* @return True if the trace tag is valid.
+ *
+ * @hide
*/
public static boolean isTagEnabled(long traceTag) {
long tags = sEnabledTags;
@@ -125,6 +144,8 @@ public final class Trace {
* @param traceTag The trace tag.
* @param counterName The counter name to appear in the trace.
* @param counterValue The counter value.
+ *
+ * @hide
*/
public static void traceCounter(long traceTag, String counterName, int counterValue) {
if (isTagEnabled(traceTag)) {
@@ -133,11 +154,44 @@ public final class Trace {
}
/**
- * Writes a trace message to indicate that a given method has begun.
- * Must be followed by a call to {@link #traceEnd} using the same tag.
+ * Set whether application tracing is allowed for this process. This is intended to be set
+ * once at application start-up time based on whether the application is debuggable.
+ *
+ * @hide
+ */
+ public static void setAppTracingAllowed(boolean allowed) {
+ nativeSetAppTracingAllowed(allowed);
+
+ // Setting whether app tracing is allowed may change the tags, so we update the cached
+ // tags here.
+ cacheEnabledTags();
+ }
+
+ /**
+ * Set whether tracing is enabled in this process. Tracing is disabled shortly after Zygote
+ * initializes and re-enabled after processes fork from Zygote. This is done because Zygote
+ * has no way to be notified about changes to the tracing tags, and if Zygote ever reads and
+ * caches the tracing tags, forked processes will inherit those stale tags.
+ *
+ * @hide
+ */
+ public static void setTracingEnabled(boolean enabled) {
+ nativeSetTracingEnabled(enabled);
+
+ // Setting whether tracing is enabled may change the tags, so we update the cached tags
+ // here.
+ cacheEnabledTags();
+ }
+
+ /**
+ * Writes a trace message to indicate that a given section of code has
+ * begun. Must be followed by a call to {@link #traceEnd} using the same
+ * tag.
*
* @param traceTag The trace tag.
* @param methodName The method name to appear in the trace.
+ *
+ * @hide
*/
public static void traceBegin(long traceTag, String methodName) {
if (isTagEnabled(traceTag)) {
@@ -150,10 +204,81 @@ public final class Trace {
* Must be called exactly once for each call to {@link #traceBegin} using the same tag.
*
* @param traceTag The trace tag.
+ *
+ * @hide
*/
public static void traceEnd(long traceTag) {
if (isTagEnabled(traceTag)) {
nativeTraceEnd(traceTag);
}
}
+
+ /**
+ * Writes a trace message to indicate that a given section of code has
+ * begun. Must be followed by a call to {@link #asyncTraceEnd} using the same
+ * tag. Unlike {@link #traceBegin(long, String)} and {@link #traceEnd(long)},
+ * asynchronous events do not need to be nested. The name and cookie used to
+ * begin an event must be used to end it.
+ *
+ * @param traceTag The trace tag.
+ * @param methodName The method name to appear in the trace.
+ * @param cookie Unique identifier for distinguishing simultaneous events
+ *
+ * @hide
+ */
+ public static void asyncTraceBegin(long traceTag, String methodName, int cookie) {
+ if (isTagEnabled(traceTag)) {
+ nativeAsyncTraceBegin(traceTag, methodName, cookie);
+ }
+ }
+
+ /**
+ * Writes a trace message to indicate that the current method has ended.
+ * Must be called exactly once for each call to {@link #asyncTraceBegin(long, String, int)}
+ * using the same tag, name and cookie.
+ *
+ * @param traceTag The trace tag.
+ * @param methodName The method name to appear in the trace.
+ * @param cookie Unique identifier for distinguishing simultaneous events
+ *
+ * @hide
+ */
+ public static void asyncTraceEnd(long traceTag, String methodName, int cookie) {
+ if (isTagEnabled(traceTag)) {
+ nativeAsyncTraceEnd(traceTag, methodName, cookie);
+ }
+ }
+
+ /**
+ * Writes a trace message to indicate that a given section of code has begun. This call must
+ * be followed by a corresponding call to {@link #endSection()} on the same thread.
+ *
+ * <p class="note"> At this time the vertical bar character '|', newline character '\n', and
+ * null character '\0' are used internally by the tracing mechanism. If sectionName contains
+ * these characters they will be replaced with a space character in the trace.
+ *
+ * @param sectionName The name of the code section to appear in the trace. This may be at
+ * most 127 Unicode code units long.
+ */
+ public static void beginSection(String sectionName) {
+ if (isTagEnabled(TRACE_TAG_APP)) {
+ if (sectionName.length() > MAX_SECTION_NAME_LEN) {
+ throw new IllegalArgumentException("sectionName is too long");
+ }
+ nativeTraceBegin(TRACE_TAG_APP, sectionName);
+ }
+ }
+
+ /**
+ * Writes a trace message to indicate that a given section of code has ended. This call must
+ * be preceeded by a corresponding call to {@link #beginSection(String)}. Calling this method
+ * will mark the end of the most recently begun section of code, so care must be taken to
+ * ensure that beginSection / endSection pairs are properly nested and called from the same
+ * thread.
+ */
+ public static void endSection() {
+ if (isTagEnabled(TRACE_TAG_APP)) {
+ nativeTraceEnd(TRACE_TAG_APP);
+ }
+ }
}
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index cc96152..d205253 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -16,6 +16,8 @@
package android.os;
+import java.io.PrintWriter;
+
/**
* Representation of a user on the device.
*/
@@ -152,6 +154,50 @@ public final class UserHandle implements Parcelable {
}
/**
+ * Generate a text representation of the uid, breaking out its individual
+ * components -- user, app, isolated, etc.
+ * @hide
+ */
+ public static void formatUid(StringBuilder sb, int uid) {
+ if (uid < Process.FIRST_APPLICATION_UID) {
+ sb.append(uid);
+ } else {
+ sb.append('u');
+ sb.append(getUserId(uid));
+ final int appId = getAppId(uid);
+ if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) {
+ sb.append('i');
+ sb.append(appId - Process.FIRST_ISOLATED_UID);
+ } else {
+ sb.append('a');
+ sb.append(appId);
+ }
+ }
+ }
+
+ /**
+ * Generate a text representation of the uid, breaking out its individual
+ * components -- user, app, isolated, etc.
+ * @hide
+ */
+ public static void formatUid(PrintWriter pw, int uid) {
+ if (uid < Process.FIRST_APPLICATION_UID) {
+ pw.print(uid);
+ } else {
+ pw.print('u');
+ pw.print(getUserId(uid));
+ final int appId = getAppId(uid);
+ if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) {
+ pw.print('i');
+ pw.print(appId - Process.FIRST_ISOLATED_UID);
+ } else {
+ pw.print('a');
+ pw.print(appId);
+ }
+ }
+ }
+
+ /**
* Returns the user id of the current process
* @return user id of the current process
* @hide
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d73f99a..cb5ed4f 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -15,15 +15,16 @@
*/
package android.os;
-import com.android.internal.R;
-
import android.app.ActivityManagerNative;
import android.content.Context;
+import android.content.RestrictionEntry;
import android.content.pm.UserInfo;
-import android.graphics.Bitmap;
import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.util.Log;
+import com.android.internal.R;
+
import java.util.List;
/**
@@ -35,6 +36,120 @@ public class UserManager {
private final IUserManager mService;
private final Context mContext;
+ /**
+ * Key for user restrictions. Specifies if a user is disallowed from adding and removing
+ * accounts.
+ * The default value is <code>false</code>.
+ * <p/>
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";
+
+ /**
+ * Key for user restrictions. Specifies if a user is disallowed from changing Wi-Fi
+ * access points.
+ * The default value is <code>false</code>.
+ * <p/>
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi";
+
+ /**
+ * Key for user restrictions. Specifies if a user is disallowed from installing applications.
+ * The default value is <code>false</code>.
+ * <p/>
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_INSTALL_APPS = "no_install_apps";
+
+ /**
+ * Key for user restrictions. Specifies if a user is disallowed from uninstalling applications.
+ * The default value is <code>false</code>.
+ * <p/>
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
+
+ /**
+ * Key for user restrictions. Specifies if a user is disallowed from toggling location sharing.
+ * The default value is <code>false</code>.
+ * <p/>
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+
+ public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
+
+ /**
+ * Key for user restrictions. Specifies if a user is disallowed from enabling the
+ * "Unknown Sources" setting, that allows installation of apps from unknown sources.
+ * The default value is <code>false</code>.
+ * <p/>
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_INSTALL_UNKNOWN_SOURCES = "no_install_unknown_sources";
+
+ /**
+ * Key for user restrictions. Specifies if a user is disallowed from configuring bluetooth.
+ * The default value is <code>false</code>.
+ * <p/>
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
+
+ /**
+ * Key for user restrictions. Specifies if a user is disallowed from transferring files over
+ * USB. The default value is <code>false</code>.
+ * <p/>
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
+
+ /**
+ * Key for user restrictions. Specifies if a user is disallowed from configuring user
+ * credentials. The default value is <code>false</code>.
+ * <p/>
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
+
+ /**
+ * Key for user restrictions. Specifies if a user is disallowed from removing users.
+ * The default value is <code>false</code>.
+ * <p/>
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_REMOVE_USER = "no_remove_user";
+
+ private static UserManager sInstance = null;
+
+ /** @hide */
+ public synchronized static UserManager get(Context context) {
+ if (sInstance == null) {
+ sInstance = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ }
+ return sInstance;
+ }
+
/** @hide */
public UserManager(Context context, IUserManager service) {
mService = service;
@@ -50,11 +165,11 @@ public class UserManager {
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();
}
@@ -77,12 +192,27 @@ public class UserManager {
/**
* Used to determine whether the user making this call is subject to
* teleportations.
- * @return whether the user making this call is a goat
+ * @return whether the user making this call is a goat
*/
public boolean isUserAGoat() {
return false;
}
-
+
+ /**
+ * Used to check if the user making this call is linked to another user. Linked users may have
+ * a reduced number of available apps, app restrictions and account restrictions.
+ * @return whether the user making this call is a linked user
+ * @hide
+ */
+ public boolean isLinkedUser() {
+ try {
+ return mService.isRestricted();
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not check if user is limited ", re);
+ 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
@@ -133,6 +263,87 @@ public class UserManager {
}
/**
+ * Returns the user-wide restrictions imposed on this user.
+ * @return a Bundle containing all the restrictions.
+ */
+ public Bundle getUserRestrictions() {
+ return getUserRestrictions(Process.myUserHandle());
+ }
+
+ /**
+ * Returns the user-wide restrictions imposed on the user specified by <code>userHandle</code>.
+ * @param userHandle the UserHandle of the user for whom to retrieve the restrictions.
+ * @return a Bundle containing all the restrictions.
+ */
+ public Bundle getUserRestrictions(UserHandle userHandle) {
+ try {
+ return mService.getUserRestrictions(userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get user restrictions", re);
+ return Bundle.EMPTY;
+ }
+ }
+
+ /**
+ * Sets all the user-wide restrictions for this user.
+ * Requires the MANAGE_USERS permission.
+ * @param restrictions the Bundle containing all the restrictions.
+ */
+ public void setUserRestrictions(Bundle restrictions) {
+ setUserRestrictions(restrictions, Process.myUserHandle());
+ }
+
+ /**
+ * Sets all the user-wide restrictions for the specified user.
+ * Requires the MANAGE_USERS permission.
+ * @param restrictions the Bundle containing all the restrictions.
+ * @param userHandle the UserHandle of the user for whom to set the restrictions.
+ */
+ public void setUserRestrictions(Bundle restrictions, UserHandle userHandle) {
+ try {
+ mService.setUserRestrictions(restrictions, userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not set user restrictions", re);
+ }
+ }
+
+ /**
+ * Sets the value of a specific restriction.
+ * Requires the MANAGE_USERS permission.
+ * @param key the key of the restriction
+ * @param value the value for the restriction
+ */
+ public void setUserRestriction(String key, boolean value) {
+ Bundle bundle = getUserRestrictions();
+ bundle.putBoolean(key, value);
+ setUserRestrictions(bundle);
+ }
+
+ /**
+ * @hide
+ * Sets the value of a specific restriction on a specific user.
+ * Requires the {@link android.Manifest.permission#MANAGE_USERS} permission.
+ * @param key the key of the restriction
+ * @param value the value for the restriction
+ * @param userHandle the user whose restriction is to be changed.
+ */
+ public void setUserRestriction(String key, boolean value, UserHandle userHandle) {
+ Bundle bundle = getUserRestrictions(userHandle);
+ bundle.putBoolean(key, value);
+ setUserRestrictions(bundle, userHandle);
+ }
+
+ /**
+ * @hide
+ * Returns whether the current user has been disallowed from performing certain actions
+ * or setting certain settings.
+ * @param restrictionKey the string key representing the restriction
+ */
+ public boolean hasUserRestriction(String restrictionKey) {
+ return getUserRestrictions().getBoolean(restrictionKey, false);
+ }
+
+ /**
* Return the serial number for a user. This is a device-unique
* number assigned to that user; if the user is deleted and then a new
* user created, the new users will not be given the same serial number.
@@ -326,7 +537,7 @@ public class UserManager {
* 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
+ * @return a value greater than or equal to 1
*/
public static int getMaxSupportedUsers() {
// Don't allow multiple users on certain builds
@@ -368,4 +579,45 @@ public class UserManager {
}
return -1;
}
+
+ /**
+ * Returns a Bundle containing any saved application restrictions for this user, for the
+ * given package name. Only an application with this package name can call this method.
+ * @param packageName the package name of the calling application
+ * @return a Bundle with the restrictions as key/value pairs, or null if there are no
+ * saved restrictions. The values can be of type Boolean, String or String[], depending
+ * on the restriction type, as defined by the application.
+ */
+ public Bundle getApplicationRestrictions(String packageName) {
+ try {
+ return mService.getApplicationRestrictions(packageName);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get application restrictions for package " + packageName);
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public Bundle getApplicationRestrictions(String packageName, UserHandle user) {
+ try {
+ return mService.getApplicationRestrictionsForUser(packageName, user.getIdentifier());
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get application restrictions for user " + user.getIdentifier());
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public void setApplicationRestrictions(String packageName, Bundle restrictions,
+ UserHandle user) {
+ try {
+ mService.setApplicationRestrictions(packageName, restrictions, user.getIdentifier());
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not set application restrictions for user " + user.getIdentifier());
+ }
+ }
}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index b67be4b..6650fca 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -73,6 +73,20 @@ public abstract class Vibrator {
public abstract void vibrate(long[] pattern, int repeat);
/**
+ * @hide
+ * Like {@link #vibrate(long)}, but allowing the caller to specify that
+ * the vibration is owned by someone else.
+ */
+ public abstract void vibrate(int owningUid, String owningPackage, long milliseconds);
+
+ /**
+ * @hide
+ * Like {@link #vibrate(long[], int)}, but allowing the caller to specify that
+ * the vibration is owned by someone else.
+ */
+ public abstract void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat);
+
+ /**
* Turn the vibrator off.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#VIBRATE}.
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index ba77df7..b79bdee 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1,6 +1,6 @@
package android.os;
-import com.android.internal.util.ArrayUtils;
+import android.util.Log;
import java.util.Arrays;
@@ -10,8 +10,12 @@ import java.util.Arrays;
* defined; this is an opaque container.
*/
public class WorkSource implements Parcelable {
+ static final String TAG = "WorkSource";
+ static final boolean DEBUG = false;
+
int mNum;
int[] mUids;
+ String[] mNames;
/**
* Internal statics to avoid object allocations in some operations.
@@ -47,8 +51,10 @@ public class WorkSource implements Parcelable {
mNum = orig.mNum;
if (orig.mUids != null) {
mUids = orig.mUids.clone();
+ mNames = orig.mNames != null ? orig.mNames.clone() : null;
} else {
mUids = null;
+ mNames = null;
}
}
@@ -56,11 +62,23 @@ public class WorkSource implements Parcelable {
public WorkSource(int uid) {
mNum = 1;
mUids = new int[] { uid, 0 };
+ mNames = null;
+ }
+
+ /** @hide */
+ public WorkSource(int uid, String name) {
+ if (name == null) {
+ throw new NullPointerException("Name can't be null");
+ }
+ mNum = 1;
+ mUids = new int[] { uid, 0 };
+ mNames = new String[] { name, null };
}
WorkSource(Parcel in) {
mNum = in.readInt();
mUids = in.createIntArray();
+ mNames = in.createStringArray();
}
/** @hide */
@@ -73,6 +91,11 @@ public class WorkSource implements Parcelable {
return mUids[index];
}
+ /** @hide */
+ public String getName(int index) {
+ return mNames != null ? mNames[index] : null;
+ }
+
/**
* Clear this WorkSource to be empty.
*/
@@ -91,6 +114,11 @@ public class WorkSource implements Parcelable {
for (int i = 0; i < mNum; i++) {
result = ((result << 4) | (result >>> 28)) ^ mUids[i];
}
+ if (mNames != null) {
+ for (int i = 0; i < mNum; i++) {
+ result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode();
+ }
+ }
return result;
}
@@ -106,10 +134,15 @@ public class WorkSource implements Parcelable {
}
final int[] uids1 = mUids;
final int[] uids2 = other.mUids;
+ final String[] names1 = mNames;
+ final String[] names2 = other.mNames;
for (int i=0; i<N; i++) {
if (uids1[i] != uids2[i]) {
return true;
}
+ if (names1 != null && names2 != null && !names1[i].equals(names2[i])) {
+ return true;
+ }
}
return false;
}
@@ -131,8 +164,18 @@ public class WorkSource implements Parcelable {
} else {
mUids = other.mUids.clone();
}
+ if (other.mNames != null) {
+ if (mNames != null && mNames.length >= mNum) {
+ System.arraycopy(other.mNames, 0, mNames, 0, mNum);
+ } else {
+ mNames = other.mNames.clone();
+ }
+ } else {
+ mNames = null;
+ }
} else {
mUids = null;
+ mNames = null;
}
}
@@ -141,6 +184,22 @@ public class WorkSource implements Parcelable {
mNum = 1;
if (mUids == null) mUids = new int[2];
mUids[0] = uid;
+ mNames = null;
+ }
+
+ /** @hide */
+ public void set(int uid, String name) {
+ if (name == null) {
+ throw new NullPointerException("Name can't be null");
+ }
+ mNum = 1;
+ if (mUids == null) {
+ mUids = new int[2];
+ mNames = new String[2];
+ }
+ mUids[0] = uid;
+ mNames[0] = name;
+ mNames = null;
}
/** @hide */
@@ -182,10 +241,49 @@ public class WorkSource implements Parcelable {
/** @hide */
public boolean add(int uid) {
- synchronized (sTmpWorkSource) {
- sTmpWorkSource.mUids[0] = uid;
- return updateLocked(sTmpWorkSource, false, false);
+ if (mNum <= 0) {
+ mNames = null;
+ insert(0, uid);
+ return true;
+ }
+ if (mNames != null) {
+ throw new IllegalArgumentException("Adding without name to named " + this);
+ }
+ int i = Arrays.binarySearch(mUids, 0, mNum, uid);
+ if (DEBUG) Log.d(TAG, "Adding uid " + uid + " to " + this + ": binsearch res = " + i);
+ if (i >= 0) {
+ return false;
}
+ insert(-i-1, uid);
+ return true;
+ }
+
+ /** @hide */
+ public boolean add(int uid, String name) {
+ if (mNum <= 0) {
+ insert(0, uid, name);
+ return true;
+ }
+ if (mNames == null) {
+ throw new IllegalArgumentException("Adding name to unnamed " + this);
+ }
+ int i;
+ for (i=0; i<mNum; i++) {
+ if (mUids[i] > uid) {
+ break;
+ }
+ if (mUids[i] == uid) {
+ int diff = mNames[i].compareTo(name);
+ if (diff > 0) {
+ break;
+ }
+ if (diff == 0) {
+ return false;
+ }
+ }
+ }
+ insert(i, uid, name);
+ return true;
}
/** @hide */
@@ -199,19 +297,102 @@ public class WorkSource implements Parcelable {
}
public boolean remove(WorkSource other) {
+ if (mNum <= 0 || other.mNum <= 0) {
+ return false;
+ }
+ if (mNames == null && other.mNames == null) {
+ return removeUids(other);
+ } else {
+ if (mNames == null) {
+ throw new IllegalArgumentException("Other " + other + " has names, but target "
+ + this + " does not");
+ }
+ if (other.mNames == null) {
+ throw new IllegalArgumentException("Target " + this + " has names, but other "
+ + other + " does not");
+ }
+ return removeUidsAndNames(other);
+ }
+ }
+
+ /** @hide */
+ public WorkSource stripNames() {
+ if (mNum <= 0) {
+ return new WorkSource();
+ }
+ WorkSource result = new WorkSource();
+ int lastUid = -1;
+ for (int i=0; i<mNum; i++) {
+ int uid = mUids[i];
+ if (i == 0 || lastUid != uid) {
+ result.add(uid);
+ }
+ }
+ return result;
+ }
+
+ private boolean removeUids(WorkSource other) {
int N1 = mNum;
final int[] uids1 = mUids;
final int N2 = other.mNum;
final int[] uids2 = other.mUids;
boolean changed = false;
- int i1 = 0;
- for (int i2=0; i2<N2 && i1<N1; i2++) {
+ int i1 = 0, i2 = 0;
+ if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this);
+ while (i1 < N1 && i2 < N2) {
+ if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
+ + " of " + N2);
if (uids2[i2] == uids1[i1]) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
+ + ": remove " + uids1[i1]);
N1--;
+ changed = true;
if (i1 < N1) System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
+ i2++;
+ } else if (uids2[i2] > uids1[i1]) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1");
+ i1++;
+ } else {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2");
+ i2++;
}
- while (i1 < N1 && uids2[i2] > uids1[i1]) {
+ }
+
+ mNum = N1;
+
+ return changed;
+ }
+
+ private boolean removeUidsAndNames(WorkSource other) {
+ int N1 = mNum;
+ final int[] uids1 = mUids;
+ final String[] names1 = mNames;
+ final int N2 = other.mNum;
+ final int[] uids2 = other.mUids;
+ final String[] names2 = other.mNames;
+ boolean changed = false;
+ int i1 = 0, i2 = 0;
+ if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this);
+ while (i1 < N1 && i2 < N2) {
+ if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
+ + " of " + N2 + ": " + uids1[i1] + " " + names1[i1]);
+ if (uids2[i2] == uids1[i1] && names2[i2].equals(names1[i1])) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
+ + ": remove " + uids1[i1] + " " + names1[i1]);
+ N1--;
+ changed = true;
+ if (i1 < N1) {
+ System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
+ System.arraycopy(names1, i1+1, names1, i1, N1-i1);
+ }
+ i2++;
+ } else if (uids2[i2] > uids1[i1]
+ || (uids2[i2] == uids1[i1] && names2[i2].compareTo(names1[i1]) > 0)) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1");
i1++;
+ } else {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2");
+ i2++;
}
}
@@ -221,20 +402,50 @@ public class WorkSource implements Parcelable {
}
private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) {
+ if (mNames == null && other.mNames == null) {
+ return updateUidsLocked(other, set, returnNewbs);
+ } else {
+ if (mNum > 0 && mNames == null) {
+ throw new IllegalArgumentException("Other " + other + " has names, but target "
+ + this + " does not");
+ }
+ if (other.mNum > 0 && other.mNames == null) {
+ throw new IllegalArgumentException("Target " + this + " has names, but other "
+ + other + " does not");
+ }
+ return updateUidsAndNamesLocked(other, set, returnNewbs);
+ }
+ }
+
+ private static WorkSource addWork(WorkSource cur, int newUid) {
+ if (cur == null) {
+ return new WorkSource(newUid);
+ }
+ cur.insert(cur.mNum, newUid);
+ return cur;
+ }
+
+ private boolean updateUidsLocked(WorkSource other, boolean set, boolean returnNewbs) {
int N1 = mNum;
int[] uids1 = mUids;
final int N2 = other.mNum;
final int[] uids2 = other.mUids;
boolean changed = false;
- int i1 = 0;
- for (int i2=0; i2<N2; i2++) {
- if (i1 >= N1 || uids2[i2] < uids1[i1]) {
+ int i1 = 0, i2 = 0;
+ if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set
+ + " returnNewbs=" + returnNewbs);
+ while (i1 < N1 || i2 < N2) {
+ if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
+ + " of " + N2);
+ if (i1 >= N1 || (i2 < N2 && uids2[i2] < uids1[i1])) {
// Need to insert a new uid.
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
+ + ": insert " + uids2[i2]);
changed = true;
if (uids1 == null) {
uids1 = new int[4];
uids1[0] = uids2[i2];
- } else if (i1 >= uids1.length) {
+ } else if (N1 >= uids1.length) {
int[] newuids = new int[(uids1.length*3)/2];
if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1);
if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1);
@@ -245,39 +456,37 @@ public class WorkSource implements Parcelable {
uids1[i1] = uids2[i2];
}
if (returnNewbs) {
- if (sNewbWork == null) {
- sNewbWork = new WorkSource(uids2[i2]);
- } else {
- sNewbWork.addLocked(uids2[i2]);
- }
+ sNewbWork = addWork(sNewbWork, uids2[i2]);
}
N1++;
i1++;
+ i2++;
} else {
if (!set) {
// Skip uids that already exist or are not in 'other'.
- do {
- i1++;
- } while (i1 < N1 && uids2[i2] >= uids1[i1]);
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip");
+ if (i2 < N2 && uids2[i2] == uids1[i1]) {
+ i2++;
+ }
+ i1++;
} else {
// Remove any uids that don't exist in 'other'.
int start = i1;
- while (i1 < N1 && uids2[i2] > uids1[i1]) {
- if (sGoneWork == null) {
- sGoneWork = new WorkSource(uids1[i1]);
- } else {
- sGoneWork.addLocked(uids1[i1]);
- }
+ while (i1 < N1 && (i2 >= N2 || uids2[i2] > uids1[i1])) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + uids1[i1]);
+ sGoneWork = addWork(sGoneWork, uids1[i1]);
i1++;
}
if (start < i1) {
- System.arraycopy(uids1, i1, uids1, start, i1-start);
+ System.arraycopy(uids1, i1, uids1, start, N1-i1);
N1 -= i1-start;
i1 = start;
}
// If there is a matching uid, skip it.
- if (i1 < N1 && uids2[i1] == uids1[i1]) {
+ if (i1 < N1 && i2 < N2 && uids2[i2] == uids1[i1]) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip");
i1++;
+ i2++;
}
}
}
@@ -289,21 +498,145 @@ public class WorkSource implements Parcelable {
return changed;
}
- private void addLocked(int uid) {
+ /**
+ * Returns 0 if equal, negative if 'this' is before 'other', positive if 'this' is after 'other'.
+ */
+ private int compare(WorkSource other, int i1, int i2) {
+ final int diff = mUids[i1] - other.mUids[i2];
+ if (diff != 0) {
+ return diff;
+ }
+ return mNames[i1].compareTo(other.mNames[i2]);
+ }
+
+ private static WorkSource addWork(WorkSource cur, int newUid, String newName) {
+ if (cur == null) {
+ return new WorkSource(newUid, newName);
+ }
+ cur.insert(cur.mNum, newUid, newName);
+ return cur;
+ }
+
+ private boolean updateUidsAndNamesLocked(WorkSource other, boolean set, boolean returnNewbs) {
+ final int N2 = other.mNum;
+ final int[] uids2 = other.mUids;
+ String[] names2 = other.mNames;
+ boolean changed = false;
+ int i1 = 0, i2 = 0;
+ if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set
+ + " returnNewbs=" + returnNewbs);
+ while (i1 < mNum || i2 < N2) {
+ if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + mNum + ", other @ " + i2
+ + " of " + N2);
+ int diff = -1;
+ if (i1 >= mNum || (i2 < N2 && (diff=compare(other, i1, i2)) > 0)) {
+ // Need to insert a new uid.
+ changed = true;
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum
+ + ": insert " + uids2[i2] + " " + names2[i2]);
+ insert(i1, uids2[i2], names2[i2]);
+ if (returnNewbs) {
+ sNewbWork = addWork(sNewbWork, uids2[i2], names2[i2]);
+ }
+ i1++;
+ i2++;
+ } else {
+ if (!set) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip");
+ if (i2 < N2 && diff == 0) {
+ i2++;
+ }
+ i1++;
+ } else {
+ // Remove any uids that don't exist in 'other'.
+ int start = i1;
+ while (diff < 0) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + mUids[i1]
+ + " " + mNames[i1]);
+ sGoneWork = addWork(sGoneWork, mUids[i1], mNames[i1]);
+ i1++;
+ if (i1 >= mNum) {
+ break;
+ }
+ diff = i2 < N2 ? compare(other, i1, i2) : -1;
+ }
+ if (start < i1) {
+ System.arraycopy(mUids, i1, mUids, start, mNum-i1);
+ System.arraycopy(mNames, i1, mNames, start, mNum-i1);
+ mNum -= i1-start;
+ i1 = start;
+ }
+ // If there is a matching uid, skip it.
+ if (i1 < mNum && diff == 0) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip");
+ i1++;
+ i2++;
+ }
+ }
+ }
+ }
+
+ return changed;
+ }
+
+ private void insert(int index, int uid) {
+ if (DEBUG) Log.d(TAG, "Insert in " + this + " @ " + index + " uid " + uid);
if (mUids == null) {
mUids = new int[4];
mUids[0] = uid;
mNum = 1;
- return;
- }
- if (mNum >= mUids.length) {
+ } else if (mNum >= mUids.length) {
int[] newuids = new int[(mNum*3)/2];
- System.arraycopy(mUids, 0, newuids, 0, mNum);
+ if (index > 0) {
+ System.arraycopy(mUids, 0, newuids, 0, index);
+ }
+ if (index < mNum) {
+ System.arraycopy(mUids, index, newuids, index+1, mNum-index);
+ }
mUids = newuids;
+ mUids[index] = uid;
+ mNum++;
+ } else {
+ if (index < mNum) {
+ System.arraycopy(mUids, index, mUids, index+1, mNum-index);
+ }
+ mUids[index] = uid;
+ mNum++;
}
+ }
- mUids[mNum] = uid;
- mNum++;
+ private void insert(int index, int uid, String name) {
+ if (mUids == null) {
+ mUids = new int[4];
+ mUids[0] = uid;
+ mNames = new String[4];
+ mNames[0] = name;
+ mNum = 1;
+ } else if (mNum >= mUids.length) {
+ int[] newuids = new int[(mNum*3)/2];
+ String[] newnames = new String[(mNum*3)/2];
+ if (index > 0) {
+ System.arraycopy(mUids, 0, newuids, 0, index);
+ System.arraycopy(mNames, 0, newnames, 0, index);
+ }
+ if (index < mNum) {
+ System.arraycopy(mUids, index, newuids, index+1, mNum-index);
+ System.arraycopy(mNames, index, newnames, index+1, mNum-index);
+ }
+ mUids = newuids;
+ mNames = newnames;
+ mUids[index] = uid;
+ mNames[index] = name;
+ mNum++;
+ } else {
+ if (index < mNum) {
+ System.arraycopy(mUids, index, mUids, index+1, mNum-index);
+ System.arraycopy(mNames, index, mNames, index+1, mNum-index);
+ }
+ mUids[index] = uid;
+ mNames[index] = name;
+ mNum++;
+ }
}
@Override
@@ -315,19 +648,24 @@ public class WorkSource implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mNum);
dest.writeIntArray(mUids);
+ dest.writeStringArray(mNames);
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
- result.append("{WorkSource: uids=[");
+ result.append("WorkSource{");
for (int i = 0; i < mNum; i++) {
if (i != 0) {
result.append(", ");
}
result.append(mUids[i]);
+ if (mNames != null) {
+ result.append(" ");
+ result.append(mNames[i]);
+ }
}
- result.append("]}");
+ result.append("}");
return result.toString();
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 862a95c..f5e728d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,7 +16,9 @@
package android.os.storage;
-import android.app.NotificationManager;
+import static android.net.TrafficStats.MB_IN_BYTES;
+
+import android.content.ContentResolver;
import android.content.Context;
import android.os.Environment;
import android.os.Handler;
@@ -25,6 +27,7 @@ import android.os.Message;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
@@ -54,20 +57,20 @@ import java.util.concurrent.atomic.AtomicInteger;
* {@link android.content.Context#getSystemService(java.lang.String)} with an
* argument of {@link android.content.Context#STORAGE_SERVICE}.
*/
-
-public class StorageManager
-{
+public class StorageManager {
private static final String TAG = "StorageManager";
+ private final ContentResolver mResolver;
+
/*
* Our internal MountService binder reference
*/
- final private IMountService mMountService;
+ private final IMountService mMountService;
/*
* The looper target for callbacks
*/
- Looper mTgtLooper;
+ private final Looper mTgtLooper;
/*
* Target listener for binder callbacks
@@ -308,16 +311,16 @@ public class StorageManager
*
* @hide
*/
- public StorageManager(Looper tgtLooper) throws RemoteException {
+ public StorageManager(ContentResolver resolver, Looper tgtLooper) throws RemoteException {
+ mResolver = resolver;
+ mTgtLooper = tgtLooper;
mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
if (mMountService == null) {
Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
return;
}
- mTgtLooper = tgtLooper;
}
-
/**
* Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
*
@@ -610,4 +613,36 @@ public class StorageManager
Log.w(TAG, "No primary storage defined");
return null;
}
+
+ private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
+ private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
+ private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
+
+ /**
+ * Return the number of available bytes at which the given path is
+ * considered running low on storage.
+ *
+ * @hide
+ */
+ public long getStorageLowBytes(File path) {
+ final long lowPercent = Settings.Global.getInt(mResolver,
+ Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
+ final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
+
+ final long maxLowBytes = Settings.Global.getLong(mResolver,
+ Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
+
+ return Math.min(lowBytes, maxLowBytes);
+ }
+
+ /**
+ * Return the number of available bytes at which the given path is
+ * considered full.
+ *
+ * @hide
+ */
+ public long getStorageFullBytes(File path) {
+ return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
+ DEFAULT_FULL_THRESHOLD_BYTES);
+ }
}
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 09ff7be..028317f 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -703,7 +703,13 @@ public abstract class PreferenceActivity extends ListActivity implements
* show for the initial UI.
*/
public Header onGetInitialHeader() {
- return mHeaders.get(0);
+ for (int i=0; i<mHeaders.size(); i++) {
+ Header h = mHeaders.get(i);
+ if (h.fragment != null) {
+ return h;
+ }
+ }
+ throw new IllegalStateException("Must have at least one header with a fragment");
}
/**
@@ -1167,6 +1173,9 @@ public abstract class PreferenceActivity extends ListActivity implements
getFragmentManager().popBackStack(BACK_STACK_PREFS,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
+ if (header.fragment == null) {
+ throw new IllegalStateException("can't switch to header that has no fragment");
+ }
int direction = mHeaders.indexOf(header) - mHeaders.indexOf(mCurHeader);
switchToHeaderInner(header.fragment, header.fragmentArguments, direction);
setSelectedHeader(header);
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 0fa5799..25af209 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -299,9 +299,16 @@ public final class CalendarContract {
* Used to indicate that local, unsynced, changes are present.
* <P>Type: INTEGER (long)</P>
*/
+
public static final String DIRTY = "dirty";
/**
+ * Used in conjunction with {@link #DIRTY} to indicate what packages wrote local changes.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MUTATORS = "mutators";
+
+ /**
* Whether the row has been deleted but not synced to the server. A
* deleted row should be ignored.
* <P>
@@ -522,6 +529,7 @@ public final class CalendarContract {
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, MUTATORS);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC1);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC2);
@@ -539,6 +547,8 @@ public final class CalendarContract {
Calendars.CALENDAR_DISPLAY_NAME);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
Calendars.CALENDAR_COLOR);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ Calendars.CALENDAR_COLOR_KEY);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, CALENDAR_ACCESS_LEVEL);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBLE);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SYNC_EVENTS);
@@ -644,6 +654,7 @@ public final class CalendarContract {
* <li>{@link #CALENDAR_COLOR}</li>
* <li>{@link #_SYNC_ID}</li>
* <li>{@link #DIRTY}</li>
+ * <li>{@link #MUTATORS}</li>
* <li>{@link #OWNER_ACCOUNT}</li>
* <li>{@link #MAX_REMINDERS}</li>
* <li>{@link #ALLOWED_REMINDERS}</li>
@@ -711,6 +722,7 @@ public final class CalendarContract {
ACCOUNT_TYPE,
_SYNC_ID,
DIRTY,
+ MUTATORS,
OWNER_ACCOUNT,
MAX_REMINDERS,
ALLOWED_REMINDERS,
@@ -1365,6 +1377,8 @@ public final class CalendarContract {
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ALL_DAY);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ACCESS_LEVEL);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, AVAILABILITY);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, EVENT_COLOR);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_COLOR_KEY);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HAS_ALARM);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
HAS_EXTENDED_PROPERTIES);
@@ -1390,6 +1404,7 @@ public final class CalendarContract {
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, IS_ORGANIZER);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, MUTATORS);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_SYNCED);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA1);
@@ -1596,6 +1611,7 @@ public final class CalendarContract {
* The following Events columns are writable only by a sync adapter
* <ul>
* <li>{@link #DIRTY}</li>
+ * <li>{@link #MUTATORS}</li>
* <li>{@link #_SYNC_ID}</li>
* <li>{@link #SYNC_DATA1}</li>
* <li>{@link #SYNC_DATA2}</li>
@@ -1685,6 +1701,7 @@ public final class CalendarContract {
public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
_SYNC_ID,
DIRTY,
+ MUTATORS,
SYNC_DATA1,
SYNC_DATA2,
SYNC_DATA3,
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index e3053be..220b997 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -35,9 +35,7 @@ import android.database.Cursor;
import android.database.DatabaseUtils;
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;
@@ -939,6 +937,15 @@ public final class ContactsContract {
* its row id changed as a result of a sync or aggregation.
*/
public static final String LOOKUP_KEY = "lookup";
+
+ /**
+ * Timestamp (milliseconds since epoch) of when this contact was last updated. This
+ * includes updates to all data associated with this contact including raw contacts. Any
+ * modification (including deletes and inserts) of underlying contact data are also
+ * reflected in this timestamp.
+ */
+ public static final String CONTACT_LAST_UPDATED_TIMESTAMP =
+ "contact_last_updated_timestamp";
}
/**
@@ -2113,6 +2120,56 @@ public final class ContactsContract {
return id >= Profile.MIN_ID;
}
+ protected interface DeletedContactsColumns {
+
+ /**
+ * A reference to the {@link ContactsContract.Contacts#_ID} that was deleted.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String CONTACT_ID = "contact_id";
+
+ /**
+ * Time (milliseconds since epoch) that the contact was deleted.
+ */
+ public static final String CONTACT_DELETED_TIMESTAMP = "contact_deleted_timestamp";
+ }
+
+ /**
+ * Constants for the deleted contact table. This table holds a log of deleted contacts.
+ * <p>
+ * Log older than {@link #DAYS_KEPT_MILLISECONDS} may be deleted.
+ */
+ public static final class DeletedContacts implements DeletedContactsColumns {
+
+ /**
+ * This utility class cannot be instantiated
+ */
+ private DeletedContacts() {
+ }
+
+ /**
+ * The content:// style URI for this table, which requests a directory of raw contact rows
+ * matching the selection criteria.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI,
+ "deleted_contacts");
+
+ /**
+ * Number of days that the delete log will be kept. After this time, delete records may be
+ * deleted.
+ *
+ * @hide
+ */
+ private static final int DAYS_KEPT = 30;
+
+ /**
+ * Milliseconds that the delete log will be kept. After this time, delete records may be
+ * deleted.
+ */
+ public static final long DAYS_KEPT_MILLISECONDS = 1000L * 60L * 60L * 24L * (long)DAYS_KEPT;
+ }
+
+
protected interface RawContactsColumns {
/**
* A reference to the {@link ContactsContract.Contacts#_ID} that this
@@ -3793,13 +3850,24 @@ public final class ContactsContract {
}
/**
+ * Columns in the Data_Usage_Stat table
+ */
+ protected interface DataUsageStatColumns {
+ /** The last time (in milliseconds) this {@link Data} was used. */
+ public static final String LAST_TIME_USED = "last_time_used";
+
+ /** The number of times the referenced {@link Data} has been used. */
+ public static final String TIMES_USED = "times_used";
+ }
+
+ /**
* Combines all columns returned by {@link ContactsContract.Data} table queries.
*
* @see ContactsContract.Data
*/
protected interface DataColumnsWithJoins extends BaseColumns, DataColumns, StatusColumns,
RawContactsColumns, ContactsColumns, ContactNameColumns, ContactOptionsColumns,
- ContactStatusColumns {
+ ContactStatusColumns, DataUsageStatColumns {
}
/**
@@ -4325,6 +4393,13 @@ public final class ContactsContract {
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "data");
/**
+ * A boolean parameter for {@link Data#CONTENT_URI}.
+ * This specifies whether or not the returned data items should be filtered to show
+ * data items belonging to visible contacts only.
+ */
+ public static final String VISIBLE_CONTACTS_ONLY = "visible_contacts_only";
+
+ /**
* The MIME type of the results from {@link #CONTENT_URI}.
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/data";
@@ -6833,6 +6908,38 @@ public final class ContactsContract {
public static final Uri CONTENT_FILTER_URI = Uri.withAppendedPath(CONTENT_URI,
"filter");
}
+
+ /**
+ * A special class of data items, used to refer to types of data that can be used to attempt
+ * to start communicating with a person ({@link Phone} and {@link Email}). Note that this
+ * is NOT a separate data kind.
+ *
+ * This URI allows the ContactsProvider to return a unified result for data items that users
+ * can use to initiate communications with another contact. {@link Phone} and {@link Email}
+ * are the current data types in this category.
+ */
+ public static final class Contactables implements DataColumnsWithJoins, CommonColumns {
+ /**
+ * The content:// style URI for these data items, which requests a directory of data
+ * rows matching the selection criteria.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(Data.CONTENT_URI,
+ "contactables");
+
+ /**
+ * The content:// style URI for these data items, which allows for a query parameter to
+ * be appended onto the end to filter for data items matching the query.
+ */
+ public static final Uri CONTENT_FILTER_URI = Uri.withAppendedPath(
+ Contactables.CONTENT_URI, "filter");
+
+ /**
+ * A boolean parameter for {@link Data#CONTENT_URI}.
+ * This specifies whether or not the returned data items should be filtered to show
+ * data items belonging to visible contacts only.
+ */
+ public static final String VISIBLE_CONTACTS_ONLY = "visible_contacts_only";
+ }
}
/**
@@ -7859,6 +7966,13 @@ public final class ContactsContract {
"android.provider.Contacts.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED";
/**
+ * This is the intent that is fired when the contacts database is created. <p> The
+ * READ_CONTACT permission is required to receive these broadcasts.
+ */
+ public static final String CONTACTS_DATABASE_CREATED =
+ "android.provider.Contacts.DATABASE_CREATED";
+
+ /**
* Starts an Activity that lets the user pick a contact to attach an image to.
* After picking the contact it launches the image cropper in face detection mode.
*/
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 31ad12b..9999760 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -407,6 +407,9 @@ public final class Downloads {
*/
public static final String COLUMN_LAST_UPDATESRC = "lastUpdateSrc";
+ /** The column that is used to count retries */
+ public static final String COLUMN_FAILED_CONNECTIONS = "numfailed";
+
/**
* default value for {@link #COLUMN_LAST_UPDATESRC}.
* This value is used when this column's value is not relevant.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4dbc4b4..8b28e21 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -50,7 +50,6 @@ import android.speech.tts.TextToSpeech;
import android.text.TextUtils;
import android.util.AndroidException;
import android.util.Log;
-import android.view.WindowOrientationListener;
import com.android.internal.widget.ILockSettings;
@@ -456,6 +455,18 @@ public final class Settings {
"android.settings.APPLICATION_DETAILS_SETTINGS";
/**
+ * @hide
+ * Activity Action: Show the "app ops" settings screen.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_APP_OPS_SETTINGS =
+ "android.settings.APP_OPS_SETTINGS";
+
+ /**
* Activity Action: Show settings for system update functionality.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -500,6 +511,9 @@ public final class Settings {
* extra to the Intent with one or more syncable content provider's authorities. Only account
* types which can sync with that content provider will be offered to the user.
* <p>
+ * Account types can also be filtered by adding an {@link #EXTRA_ACCOUNT_TYPES} extra to the
+ * Intent with one or more account types.
+ * <p>
* Input: Nothing.
* <p>
* Output: Nothing.
@@ -636,11 +650,26 @@ public final class Settings {
* <p>
* Output: Nothing.
* @see android.service.dreams.DreamService
- * @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
+ /**
+ * Activity Action: Show Notification listener settings.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ * @see android.service.notification.NotificationListenerService
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS
+ = "android.settings.NOTIFICATION_LISTENER_SETTINGS";
+
// End of Intent actions for Settings
/**
@@ -682,8 +711,20 @@ public final class Settings {
* Example: The {@link #ACTION_ADD_ACCOUNT} intent restricts the account types available based
* on the authority given.
*/
- public static final String EXTRA_AUTHORITIES =
- "authorities";
+ public static final String EXTRA_AUTHORITIES = "authorities";
+
+ /**
+ * Activity Extra: Limit available options in launched activity based on the given account
+ * types.
+ * <p>
+ * This can be passed as an extra field in an Activity Intent with one or more account types
+ * as a String[]. This field is used by some intents to alter the behavior of the called
+ * activity.
+ * <p>
+ * Example: The {@link #ACTION_ADD_ACCOUNT} intent restricts the account types to the specified
+ * list.
+ */
+ public static final String EXTRA_ACCOUNT_TYPES = "account_types";
public static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
@@ -774,7 +815,7 @@ public final class Settings {
arg.putString(Settings.NameValueTable.VALUE, value);
arg.putInt(CALL_METHOD_USER_KEY, userHandle);
IContentProvider cp = lazyGetProvider(cr);
- cp.call(mCallSetCommand, name, arg);
+ cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
} catch (RemoteException e) {
Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
return false;
@@ -821,7 +862,7 @@ public final class Settings {
args = new Bundle();
args.putInt(CALL_METHOD_USER_KEY, userHandle);
}
- Bundle b = cp.call(mCallGetCommand, name, args);
+ Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
if (b != null) {
String value = b.getPairValue();
// Don't update our cache for reads of other users' data
@@ -846,7 +887,7 @@ public final class Settings {
Cursor c = null;
try {
- c = cp.query(mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
+ c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
new String[]{name}, null, null);
if (c == null) {
Log.w(TAG, "Can't get key " + name + " from " + mUri);
@@ -2610,6 +2651,7 @@ public final class Settings {
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.BUGREPORT_IN_POWER_MENU);
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);
@@ -2659,13 +2701,6 @@ public final class Settings {
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);
@@ -3081,8 +3116,10 @@ public final class Settings {
/**
* When the user has enable the option to have a "bug report" command
* in the power menu.
+ * @deprecated Use {@link android.provider.Settings.Global#BUGREPORT_IN_POWER_MENU} instead
* @hide
*/
+ @Deprecated
public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
/**
@@ -3233,6 +3270,7 @@ public final class Settings {
/**
* This preference contains the string that shows for owner info on LockScreen.
* @hide
+ * @deprecated
*/
public static final String LOCK_SCREEN_OWNER_INFO = "lock_screen_owner_info";
@@ -3260,6 +3298,7 @@ public final class Settings {
/**
* This preference enables showing the owner info on LockScreen.
* @hide
+ * @deprecated
*/
public static final String LOCK_SCREEN_OWNER_INFO_ENABLED =
"lock_screen_owner_info_enabled";
@@ -4028,6 +4067,21 @@ public final class Settings {
public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component";
/**
+ * Name of a package that the current user has explicitly allowed to see all of that
+ * user's notifications.
+ *
+ * @hide
+ */
+ public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
+
+ /**
+ * Whether or not to enable the dial pad autocomplete functionality.
+ *
+ * @hide
+ */
+ public static final String DIALPAD_AUTOCOMPLETE = "dialpad_autocomplete";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -4037,7 +4091,7 @@ public final class Settings {
* @hide
*/
public static final String[] SETTINGS_TO_BACKUP = {
- BUGREPORT_IN_POWER_MENU,
+ BUGREPORT_IN_POWER_MENU, // moved to global
ALLOW_MOCK_LOCATION,
PARENTAL_CONTROL_ENABLED,
PARENTAL_CONTROL_REDIRECT_URL,
@@ -4068,8 +4122,7 @@ public final class Settings {
MOUNT_UMS_PROMPT,
MOUNT_UMS_NOTIFY_ENABLED,
UI_NIGHT_MODE,
- LOCK_SCREEN_OWNER_INFO,
- LOCK_SCREEN_OWNER_INFO_ENABLED
+ DIALPAD_AUTOCOMPLETE
};
/**
@@ -4316,6 +4369,13 @@ public final class Settings {
public static final String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
/**
+ * When the user has enable the option to have a "bug report" command
+ * in the power menu.
+ * @hide
+ */
+ public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
+
+ /**
* Whether ADB is enabled.
*/
public static final String ADB_ENABLED = "adb_enabled";
@@ -4385,6 +4445,14 @@ public final class Settings {
public static final String DATA_ROAMING = "data_roaming";
/**
+ * The value passed to a Mobile DataConnection via bringUp which defines the
+ * number of retries to preform when setting up the initial connection. The default
+ * value defined in DataConnectionTrackerBase#DEFAULT_MDC_INITIAL_RETRY is currently 1.
+ * @hide
+ */
+ public static final String MDC_INITIAL_MAX_RETRY = "mdc_initial_max_retry";
+
+ /**
* Whether user has enabled development settings.
*/
public static final String DEVELOPMENT_SETTINGS_ENABLED = "development_settings_enabled";
@@ -4685,50 +4753,6 @@ public final class Settings {
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";
@@ -4810,6 +4834,13 @@ public final class Settings {
public static final String WIFI_ON = "wifi_on";
/**
+ * Setting to allow scans to be enabled even wifi is turned off for connectivity.
+ * @hide
+ */
+ public static final String WIFI_SCAN_ALWAYS_AVAILABLE =
+ "wifi_scan_always_enabled";
+
+ /**
* Used to save the Wifi_ON state prior to tethering.
* This state will be checked to restore Wifi after
* the user turns off tethering.
@@ -4884,6 +4915,12 @@ public final class Settings {
public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
/**
+ * The min time between wifi disable and wifi enable
+ * @hide
+ */
+ public static final String WIFI_REENABLE_DELAY_MS = "wifi_reenable_delay";
+
+ /**
* The number of milliseconds to delay when checking for data stalls during
* non-aggressive detection. (screen is turned off.)
* @hide
@@ -4900,6 +4937,13 @@ public final class Settings {
"data_stall_alarm_aggressive_delay_in_ms";
/**
+ * The number of milliseconds to allow the provisioning apn to remain active
+ * @hide
+ */
+ public static final String PROVISIONING_APN_ALARM_DELAY_IN_MS =
+ "provisioning_apn_alarm_delay_in_ms";
+
+ /**
* The interval in milliseconds at which to check gprs registration
* after the first registration mismatch of gprs and voice service,
* to detect possible data network registration problems.
@@ -5343,6 +5387,76 @@ public final class Settings {
public static final String AUDIO_SAFE_VOLUME_STATE = "audio_safe_volume_state";
/**
+ * URL for tzinfo (time zone) updates
+ * @hide
+ */
+ public static final String TZINFO_UPDATE_CONTENT_URL = "tzinfo_content_url";
+
+ /**
+ * URL for tzinfo (time zone) update metadata
+ * @hide
+ */
+ public static final String TZINFO_UPDATE_METADATA_URL = "tzinfo_metadata_url";
+
+ /**
+ * URL for selinux (mandatory access control) updates
+ * @hide
+ */
+ public static final String SELINUX_UPDATE_CONTENT_URL = "selinux_content_url";
+
+ /**
+ * URL for selinux (mandatory access control) update metadata
+ * @hide
+ */
+ public static final String SELINUX_UPDATE_METADATA_URL = "selinux_metadata_url";
+
+ /**
+ * URL for sms short code updates
+ * @hide
+ */
+ public static final String SMS_SHORT_CODES_UPDATE_CONTENT_URL =
+ "sms_short_codes_content_url";
+
+ /**
+ * URL for sms short code update metadata
+ * @hide
+ */
+ public static final String SMS_SHORT_CODES_UPDATE_METADATA_URL =
+ "sms_short_codes_metadata_url";
+
+ /**
+ * URL for cert pinlist updates
+ * @hide
+ */
+ public static final String CERT_PIN_UPDATE_CONTENT_URL = "cert_pin_content_url";
+
+ /**
+ * URL for cert pinlist updates
+ * @hide
+ */
+ public static final String CERT_PIN_UPDATE_METADATA_URL = "cert_pin_metadata_url";
+
+ /**
+ * URL for intent firewall updates
+ * @hide
+ */
+ public static final String INTENT_FIREWALL_UPDATE_CONTENT_URL =
+ "intent_firewall_content_url";
+
+ /**
+ * URL for intent firewall update metadata
+ * @hide
+ */
+ public static final String INTENT_FIREWALL_UPDATE_METADATA_URL =
+ "intent_firewall_metadata_url";
+
+ /**
+ * SELinux enforcement status. If 0, permissive; if 1, enforcing.
+ * @hide
+ */
+ public static final String SELINUX_STATUS = "selinux_status";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
@@ -5359,6 +5473,7 @@ public final class Settings {
* @hide
*/
public static final String[] SETTINGS_TO_BACKUP = {
+ BUGREPORT_IN_POWER_MENU,
STAY_ON_WHILE_PLUGGED_IN,
MODE_RINGER,
AUTO_TIME,
diff --git a/core/java/android/security/IKeystoreService.java b/core/java/android/security/IKeystoreService.java
index a890d9b..3d75dc8 100644
--- a/core/java/android/security/IKeystoreService.java
+++ b/core/java/android/security/IKeystoreService.java
@@ -78,7 +78,7 @@ public interface IKeystoreService extends IInterface {
return _result;
}
- public int insert(String name, byte[] item, int uid) throws RemoteException {
+ public int insert(String name, byte[] item, int uid, int flags) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
int _result;
@@ -87,6 +87,7 @@ public interface IKeystoreService extends IInterface {
_data.writeString(name);
_data.writeByteArray(item);
_data.writeInt(uid);
+ _data.writeInt(flags);
mRemote.transact(Stub.TRANSACTION_insert, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
@@ -243,7 +244,7 @@ public interface IKeystoreService extends IInterface {
return _result;
}
- public int generate(String name, int uid) throws RemoteException {
+ public int generate(String name, int uid, int flags) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
int _result;
@@ -251,6 +252,7 @@ public interface IKeystoreService extends IInterface {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
_data.writeInt(uid);
+ _data.writeInt(flags);
mRemote.transact(Stub.TRANSACTION_generate, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
@@ -261,7 +263,8 @@ public interface IKeystoreService extends IInterface {
return _result;
}
- public int import_key(String name, byte[] data, int uid) throws RemoteException {
+ public int import_key(String name, byte[] data, int uid, int flags)
+ throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
int _result;
@@ -270,6 +273,7 @@ public interface IKeystoreService extends IInterface {
_data.writeString(name);
_data.writeByteArray(data);
_data.writeInt(uid);
+ _data.writeInt(flags);
mRemote.transact(Stub.TRANSACTION_import, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
@@ -427,6 +431,41 @@ public interface IKeystoreService extends IInterface {
}
return _result;
}
+
+ @Override
+ public int is_hardware_backed() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_is_hardware_backed, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ @Override
+ public int clear_uid(long uid) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeLong(uid);
+ mRemote.transact(Stub.TRANSACTION_clear_uid, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
}
private static final String DESCRIPTOR = "android.security.keystore";
@@ -452,6 +491,8 @@ public interface IKeystoreService extends IInterface {
static final int TRANSACTION_ungrant = IBinder.FIRST_CALL_TRANSACTION + 18;
static final int TRANSACTION_getmtime = IBinder.FIRST_CALL_TRANSACTION + 19;
static final int TRANSACTION_duplicate = IBinder.FIRST_CALL_TRANSACTION + 20;
+ static final int TRANSACTION_is_hardware_backed = IBinder.FIRST_CALL_TRANSACTION + 21;
+ static final int TRANSACTION_clear_uid = IBinder.FIRST_CALL_TRANSACTION + 22;
/**
* Cast an IBinder object into an IKeystoreService interface, generating
@@ -501,7 +542,7 @@ public interface IKeystoreService extends IInterface {
public byte[] get(String name) throws RemoteException;
- public int insert(String name, byte[] item, int uid) throws RemoteException;
+ public int insert(String name, byte[] item, int uid, int flags) throws RemoteException;
public int del(String name, int uid) throws RemoteException;
@@ -519,9 +560,9 @@ public interface IKeystoreService extends IInterface {
public int zero() throws RemoteException;
- public int generate(String name, int uid) throws RemoteException;
+ public int generate(String name, int uid, int flags) throws RemoteException;
- public int import_key(String name, byte[] data, int uid) throws RemoteException;
+ public int import_key(String name, byte[] data, int uid, int flags) throws RemoteException;
public byte[] sign(String name, byte[] data) throws RemoteException;
@@ -539,4 +580,8 @@ public interface IKeystoreService extends IInterface {
public int duplicate(String srcKey, int srcUid, String destKey, int destUid)
throws RemoteException;
+
+ public int is_hardware_backed() throws RemoteException;
+
+ public int clear_uid(long uid) throws RemoteException;
}
diff --git a/core/java/android/server/package.html b/core/java/android/server/package.html
deleted file mode 100644
index c9f96a6..0000000
--- a/core/java/android/server/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<body>
-
-{@hide}
-
-</body>
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
deleted file mode 100644
index 1a10644..0000000
--- a/core/java/android/server/search/SearchManagerService.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.search;
-
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.util.IndentingPrintWriter;
-
-import android.app.ActivityManager;
-import android.app.ActivityManagerNative;
-import android.app.AppGlobals;
-import android.app.ISearchManager;
-import android.app.SearchManager;
-import android.app.SearchableInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.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.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-
-/**
- * The search manager service handles the search UI, and maintains a registry of searchable
- * activities.
- */
-public class SearchManagerService extends ISearchManager.Stub {
-
- // general debugging support
- private static final String TAG = "SearchManagerService";
-
- // Context that the service is running in.
- private final Context mContext;
-
- // This field is initialized lazily in getSearchables(), and then never modified.
- private final SparseArray<Searchables> mSearchables = new SparseArray<Searchables>();
-
- /**
- * Initializes the Search Manager service in the provided system context.
- * Only one instance of this object should be created!
- *
- * @param context to use for accessing DB, window manager, etc.
- */
- public SearchManagerService(Context context) {
- mContext = context;
- mContext.registerReceiver(new BootCompletedReceiver(),
- new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
- mContext.registerReceiver(new UserReceiver(),
- new IntentFilter(Intent.ACTION_USER_REMOVED));
- new MyPackageMonitor().register(context, null, UserHandle.ALL, true);
- }
-
- private Searchables getSearchables(int userId) {
- long origId = Binder.clearCallingIdentity();
- try {
- boolean userExists = ((UserManager) mContext.getSystemService(Context.USER_SERVICE))
- .getUserInfo(userId) != null;
- if (!userExists) return null;
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- synchronized (mSearchables) {
- Searchables searchables = mSearchables.get(userId);
-
- if (searchables == null) {
- //Log.i(TAG, "Building list of searchable activities for userId=" + userId);
- searchables = new Searchables(mContext, userId);
- searchables.buildSearchableList();
- mSearchables.append(userId, searchables);
- }
- return searchables;
- }
- }
-
- private void onUserRemoved(int userId) {
- if (userId != UserHandle.USER_OWNER) {
- synchronized (mSearchables) {
- mSearchables.remove(userId);
- }
- }
- }
-
- /**
- * Creates the initial searchables list after boot.
- */
- private final class BootCompletedReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- new Thread() {
- @Override
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- mContext.unregisterReceiver(BootCompletedReceiver.this);
- getSearchables(0);
- }
- }.start();
- }
- }
-
- private final class UserReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_OWNER));
- }
- }
-
- /**
- * Refreshes the "searchables" list when packages are added/removed.
- */
- class MyPackageMonitor extends PackageMonitor {
-
- @Override
- public void onSomePackagesChanged() {
- updateSearchables();
- }
-
- @Override
- public void onPackageModified(String pkg) {
- updateSearchables();
- }
-
- private void updateSearchables() {
- final int changingUserId = getChangingUserId();
- synchronized (mSearchables) {
- // Update list of searchable activities
- for (int i = 0; i < mSearchables.size(); i++) {
- if (changingUserId == mSearchables.keyAt(i)) {
- getSearchables(mSearchables.keyAt(i)).buildSearchableList();
- break;
- }
- }
- }
- // 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
- | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcastAsUser(intent, new UserHandle(changingUserId));
- }
- }
-
- class GlobalSearchProviderObserver extends ContentObserver {
- private final ContentResolver mResolver;
-
- public GlobalSearchProviderObserver(ContentResolver resolver) {
- super(null);
- mResolver = resolver;
- mResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY),
- false /* notifyDescendants */,
- this);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- synchronized (mSearchables) {
- 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.sendBroadcastAsUser(intent, UserHandle.ALL);
- }
-
- }
-
- //
- // Searchable activities API
- //
-
- /**
- * Returns the SearchableInfo for a given activity.
- *
- * @param launchActivity The activity from which we're launching this search.
- * @return Returns a SearchableInfo record describing the parameters of the search,
- * or null if no searchable metadata was available.
- */
- public SearchableInfo getSearchableInfo(final ComponentName launchActivity) {
- if (launchActivity == null) {
- Log.e(TAG, "getSearchableInfo(), activity == null");
- return null;
- }
- 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(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList();
- }
-
- public List<ResolveInfo> getGlobalSearchActivities() {
- return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities();
- }
-
- /**
- * Gets the name of the global search activity.
- */
- public ComponentName getGlobalSearchActivity() {
- return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity();
- }
-
- /**
- * Gets the name of the web search activity.
- */
- public ComponentName getWebSearchActivity() {
- return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity();
- }
-
- @Override
- public ComponentName getAssistIntent(int userHandle) {
- try {
- userHandle = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), userHandle, true, false, "getAssistIntent", 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;
- }
-
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
-
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- synchronized (mSearchables) {
- for (int i = 0; i < mSearchables.size(); i++) {
- ipw.print("\nUser: "); ipw.println(mSearchables.keyAt(i));
- ipw.increaseIndent();
- mSearchables.valueAt(i).dump(fd, ipw, args);
- ipw.decreaseIndent();
- }
- }
- }
-}
diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java
deleted file mode 100644
index a0095d6..0000000
--- a/core/java/android/server/search/Searchables.java
+++ /dev/null
@@ -1,464 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.search;
-
-import android.app.AppGlobals;
-import android.app.SearchManager;
-import android.app.SearchableInfo;
-import android.content.ComponentName;
-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;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * This class maintains the information about all searchable activities.
- * This is a hidden class.
- */
-public class Searchables {
-
- private static final String LOG_TAG = "Searchables";
-
- // static strings used for XML lookups, etc.
- // TODO how should these be documented for the developer, in a more structured way than
- // the current long wordy javadoc in SearchManager.java ?
- private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable";
- private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
-
- private Context mContext;
-
- private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
- private ArrayList<SearchableInfo> mSearchablesList = null;
- private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
- // Contains all installed activities that handle the global search
- // intent.
- private List<ResolveInfo> mGlobalSearchActivities;
- private ComponentName mCurrentGlobalSearchActivity = null;
- private ComponentName mWebSearchActivity = null;
-
- public static String GOOGLE_SEARCH_COMPONENT_NAME =
- "com.android.googlesearch/.GoogleSearch";
- 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, int userId) {
- mContext = context;
- mUserId = userId;
- mPm = AppGlobals.getPackageManager();
- }
-
- /**
- * Look up, or construct, based on the activity.
- *
- * The activities fall into three cases, based on meta-data found in
- * the manifest entry:
- * <ol>
- * <li>The activity itself implements search. This is indicated by the
- * presence of a "android.app.searchable" meta-data attribute.
- * The value is a reference to an XML file containing search information.</li>
- * <li>A related activity implements search. This is indicated by the
- * presence of a "android.app.default_searchable" meta-data attribute.
- * The value is a string naming the activity implementing search. In this
- * case the factory will "redirect" and return the searchable data.</li>
- * <li>No searchability data is provided. We return null here and other
- * code will insert the "default" (e.g. contacts) search.
- *
- * TODO: cache the result in the map, and check the map first.
- * TODO: it might make sense to implement the searchable reference as
- * an application meta-data entry. This way we don't have to pepper each
- * and every activity.
- * TODO: can we skip the constructor step if it's a non-searchable?
- * TODO: does it make sense to plug the default into a slot here for
- * automatic return? Probably not, but it's one way to do it.
- *
- * @param activity The name of the current activity, or null if the
- * activity does not define any explicit searchable metadata.
- */
- public SearchableInfo getSearchableInfo(ComponentName activity) {
- // Step 1. Is the result already hashed? (case 1)
- SearchableInfo result;
- synchronized (this) {
- result = mSearchablesMap.get(activity);
- if (result != null) return result;
- }
-
- // Step 2. See if the current activity references a searchable.
- // Note: Conceptually, this could be a while(true) loop, but there's
- // no point in implementing reference chaining here and risking a loop.
- // References must point directly to searchable activities.
-
- ActivityInfo ai = null;
- try {
- 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;
- 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);
- }
-
- // 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;
- }
- }
- }
-
- // Step 3. None found. Return null.
- return null;
-
- }
-
- /**
- * Builds an entire list (suitable for display) of
- * activities that are searchable, by iterating the entire set of
- * ACTION_SEARCH & ACTION_WEB_SEARCH intents.
- *
- * Also clears the hash of all activities -> searches which will
- * refill as the user clicks "search".
- *
- * This should only be done at startup and again if we know that the
- * list has changed.
- *
- * TODO: every activity that provides a ACTION_SEARCH intent should
- * also provide searchability meta-data. There are a bunch of checks here
- * that, if data is not found, silently skip to the next activity. This
- * won't help a developer trying to figure out why their activity isn't
- * showing up in the list, but an exception here is too rough. I would
- * like to find a better notification mechanism.
- *
- * TODO: sort the list somehow? UI choice.
- */
- public void buildSearchableList() {
- // These will become the new values at the end of the method
- HashMap<ComponentName, SearchableInfo> newSearchablesMap
- = new HashMap<ComponentName, SearchableInfo>();
- ArrayList<SearchableInfo> newSearchablesList
- = new ArrayList<SearchableInfo>();
- ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
- = new ArrayList<SearchableInfo>();
-
- // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
- List<ResolveInfo> searchList;
- final Intent intent = new Intent(Intent.ACTION_SEARCH);
-
- long ident = Binder.clearCallingIdentity();
- try {
- searchList = queryIntentActivities(intent, PackageManager.GET_META_DATA);
-
- List<ResolveInfo> webSearchInfoList;
- final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
- 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;
- for (int ii = 0; ii < count; ii++) {
- // for each component, try to find metadata
- ResolveInfo info = (ii < search_count)
- ? searchList.get(ii)
- : webSearchInfoList.get(ii - search_count);
- ActivityInfo ai = info.activityInfo;
- // Check first to avoid duplicate entries.
- if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) {
- SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai,
- mUserId);
- if (searchable != null) {
- newSearchablesList.add(searchable);
- newSearchablesMap.put(searchable.getSearchActivity(), searchable);
- if (searchable.shouldIncludeInGlobalSearch()) {
- newSearchablesInGlobalSearchList.add(searchable);
- }
- }
- }
- }
- }
-
- List<ResolveInfo> newGlobalSearchActivities = findGlobalSearchActivities();
-
- // Find the global search activity
- ComponentName newGlobalSearchActivity = findGlobalSearchActivity(
- newGlobalSearchActivities);
-
- // Find the web search activity
- ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity);
-
- // Store a consistent set of new values
- synchronized (this) {
- mSearchablesMap = newSearchablesMap;
- mSearchablesList = newSearchablesList;
- mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
- mGlobalSearchActivities = newGlobalSearchActivities;
- mCurrentGlobalSearchActivity = newGlobalSearchActivity;
- mWebSearchActivity = newWebSearchActivity;
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * Returns a sorted list of installed search providers as per
- * the following heuristics:
- *
- * (a) System apps are given priority over non system apps.
- * (b) Among system apps and non system apps, the relative ordering
- * is defined by their declared priority.
- */
- private List<ResolveInfo> findGlobalSearchActivities() {
- // 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);
- List<ResolveInfo> activities =
- 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);
- }
-
- return activities;
- }
-
- /**
- * Finds the global search activity.
- */
- private ComponentName findGlobalSearchActivity(List<ResolveInfo> installed) {
- // Fetch the global search provider from the system settings,
- // and if it's still installed, return it.
- final String searchProviderSetting = getGlobalSearchProviderSetting();
- if (!TextUtils.isEmpty(searchProviderSetting)) {
- final ComponentName globalSearchComponent = ComponentName.unflattenFromString(
- searchProviderSetting);
- if (globalSearchComponent != null && isInstalled(globalSearchComponent)) {
- return globalSearchComponent;
- }
- }
-
- return getDefaultGlobalSearchProvider(installed);
- }
-
- /**
- * Checks whether the global search provider with a given
- * component name is installed on the system or not. This deals with
- * cases such as the removal of an installed provider.
- */
- private boolean isInstalled(ComponentName globalSearch) {
- Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
- intent.setComponent(globalSearch);
-
- List<ResolveInfo> activities = queryIntentActivities(intent,
- PackageManager.MATCH_DEFAULT_ONLY);
- if (activities != null && !activities.isEmpty()) {
- return true;
- }
-
- return false;
- }
-
- private static final Comparator<ResolveInfo> GLOBAL_SEARCH_RANKER =
- new Comparator<ResolveInfo>() {
- @Override
- public int compare(ResolveInfo lhs, ResolveInfo rhs) {
- if (lhs == rhs) {
- return 0;
- }
- boolean lhsSystem = isSystemApp(lhs);
- boolean rhsSystem = isSystemApp(rhs);
-
- if (lhsSystem && !rhsSystem) {
- return -1;
- } else if (rhsSystem && !lhsSystem) {
- return 1;
- } else {
- // Either both system engines, or both non system
- // engines.
- //
- // Note, this isn't a typo. Higher priority numbers imply
- // higher priority, but are "lower" in the sort order.
- return rhs.priority - lhs.priority;
- }
- }
- };
-
- /**
- * @return true iff. the resolve info corresponds to a system application.
- */
- private static final boolean isSystemApp(ResolveInfo res) {
- return (res.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- }
-
- /**
- * Returns the highest ranked search provider as per the
- * ranking defined in {@link #getGlobalSearchActivities()}.
- */
- private ComponentName getDefaultGlobalSearchProvider(List<ResolveInfo> providerList) {
- if (providerList != null && !providerList.isEmpty()) {
- ActivityInfo ai = providerList.get(0).activityInfo;
- return new ComponentName(ai.packageName, ai.name);
- }
-
- Log.w(LOG_TAG, "No global search activity found");
- return null;
- }
-
- private String getGlobalSearchProviderSetting() {
- return Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY);
- }
-
- /**
- * Finds the web search activity.
- *
- * Only looks in the package of the global search activity.
- */
- private ComponentName findWebSearchActivity(ComponentName globalSearchActivity) {
- if (globalSearchActivity == null) {
- return null;
- }
- Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
- intent.setPackage(globalSearchActivity.getPackageName());
- List<ResolveInfo> activities =
- queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
-
- if (activities != null && !activities.isEmpty()) {
- ActivityInfo ai = activities.get(0).activityInfo;
- // TODO: do some sanity checks here?
- return new ComponentName(ai.packageName, ai.name);
- }
- Log.w(LOG_TAG, "No web search activity found");
- 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.
- */
- public synchronized ArrayList<SearchableInfo> getSearchablesList() {
- ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList);
- return result;
- }
-
- /**
- * Returns a list of the searchable activities that can be included in global search.
- */
- public synchronized ArrayList<SearchableInfo> getSearchablesInGlobalSearchList() {
- return new ArrayList<SearchableInfo>(mSearchablesInGlobalSearchList);
- }
-
- /**
- * Returns a list of activities that handle the global search intent.
- */
- public synchronized ArrayList<ResolveInfo> getGlobalSearchActivities() {
- return new ArrayList<ResolveInfo>(mGlobalSearchActivities);
- }
-
- /**
- * Gets the name of the global search activity.
- */
- public synchronized ComponentName getGlobalSearchActivity() {
- return mCurrentGlobalSearchActivity;
- }
-
- /**
- * Gets the name of the web search activity.
- */
- public synchronized ComponentName getWebSearchActivity() {
- return mWebSearchActivity;
- }
-
- void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("Searchable authorities:");
- synchronized (this) {
- if (mSearchablesList != null) {
- for (SearchableInfo info: mSearchablesList) {
- pw.print(" "); pw.println(info.getSuggestAuthority());
- }
- }
- }
- }
-}
diff --git a/core/java/android/server/search/package.html b/core/java/android/server/search/package.html
deleted file mode 100644
index c9f96a6..0000000
--- a/core/java/android/server/search/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<body>
-
-{@hide}
-
-</body>
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
new file mode 100644
index 0000000..425fdc1
--- /dev/null
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.service.notification.StatusBarNotification;
+
+/** @hide */
+oneway interface INotificationListener
+{
+ void onNotificationPosted(in StatusBarNotification notification);
+ void onNotificationRemoved(in StatusBarNotification notification);
+} \ No newline at end of file
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
new file mode 100644
index 0000000..bfea9ca
--- /dev/null
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.annotation.SdkConstant;
+import android.app.INotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * A service that receives calls from the system when new notifications are posted or removed.
+ * <p>To extend this class, you must declare the service in your manifest file with
+ * the {@link android.Manifest.permission#BIND_NOTIFICATION_LISTENER_SERVICE} permission
+ * and include an intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * &lt;service android:name=".NotificationListener"
+ * android:label="&#64;string/service_name"
+ * android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+ * &lt;intent-filter>
+ * &lt;action android:name="android.service.notification.NotificationListenerService" />
+ * &lt;/intent-filter>
+ * &lt;/service></pre>
+ */
+public abstract class NotificationListenerService extends Service {
+ // TAG = "NotificationListenerService[MySubclass]"
+ private final String TAG = NotificationListenerService.class.getSimpleName()
+ + "[" + getClass().getSimpleName() + "]";
+
+ private INotificationListenerWrapper mWrapper = null;
+
+ private INotificationManager mNoMan;
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE
+ = "android.service.notification.NotificationListenerService";
+
+ /**
+ * Implement this method to learn about new notifications as they are posted by apps.
+ *
+ * @param sbn A data structure encapsulating the original {@link android.app.Notification}
+ * object as well as its identifying information (tag and id) and source
+ * (package name).
+ */
+ public abstract void onNotificationPosted(StatusBarNotification sbn);
+
+ /**
+ * Implement this method to learn when notifications are removed.
+ * <P>
+ * This might occur because the user has dismissed the notification using system UI (or another
+ * notification listener) or because the app has withdrawn the notification.
+ * <P>
+ * NOTE: The {@link StatusBarNotification} object you receive will be "light"; that is, the
+ * result from {@link StatusBarNotification#getNotification} may be missing some heavyweight
+ * fields such as {@link android.app.Notification#contentView} and
+ * {@link android.app.Notification#largeIcon}. However, all other fields on
+ * {@link StatusBarNotification}, sufficient to match this call with a prior call to
+ * {@link #onNotificationPosted(StatusBarNotification)}, will be intact.
+ *
+ * @param sbn A data structure encapsulating at least the original information (tag and id)
+ * and source (package name) used to post the {@link android.app.Notification} that
+ * was just removed.
+ */
+ public abstract void onNotificationRemoved(StatusBarNotification sbn);
+
+ private final INotificationManager getNotificationInterface() {
+ if (mNoMan == null) {
+ mNoMan = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ }
+ return mNoMan;
+ }
+
+ /**
+ * Inform the notification manager about dismissal of a single notification.
+ * <p>
+ * Use this if your listener has a user interface that allows the user to dismiss individual
+ * notifications, similar to the behavior of Android's status bar and notification panel.
+ * It should be called after the user dismisses a single notification using your UI;
+ * upon being informed, the notification manager will actually remove the notification
+ * and you will get an {@link #onNotificationRemoved(StatusBarNotification)} callback.
+ * <P>
+ * <b>Note:</b> If your listener allows the user to fire a notification's
+ * {@link android.app.Notification#contentIntent} by tapping/clicking/etc., you should call
+ * this method at that time <i>if</i> the Notification in question has the
+ * {@link android.app.Notification#FLAG_AUTO_CANCEL} flag set.
+ *
+ * @param pkg Package of the notifying app.
+ * @param tag Tag of the notification as specified by the notifying app in
+ * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
+ * @param id ID of the notification as specified by the notifying app in
+ * {@link android.app.NotificationManager#notify(String, int, android.app.Notification)}.
+ */
+ public final void cancelNotification(String pkg, String tag, int id) {
+ try {
+ getNotificationInterface().cancelNotificationFromListener(mWrapper, pkg, tag, id);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ }
+
+ /**
+ * Inform the notification manager about dismissal of all notifications.
+ * <p>
+ * Use this if your listener has a user interface that allows the user to dismiss all
+ * notifications, similar to the behavior of Android's status bar and notification panel.
+ * It should be called after the user invokes the "dismiss all" function of your UI;
+ * upon being informed, the notification manager will actually remove all active notifications
+ * and you will get multiple {@link #onNotificationRemoved(StatusBarNotification)} callbacks.
+ *
+ * {@see #cancelNotification(String, String, int)}
+ */
+ public final void cancelAllNotifications() {
+ try {
+ getNotificationInterface().cancelAllNotificationsFromListener(mWrapper);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ }
+
+ /**
+ * Request the list of outstanding notifications (that is, those that are visible to the
+ * current user). Useful when starting up and you don't know what's already been posted.
+ *
+ * @return An array of active notifications.
+ */
+ public StatusBarNotification[] getActiveNotifications() {
+ try {
+ return getNotificationInterface().getActiveNotificationsFromListener(mWrapper);
+ } catch (android.os.RemoteException ex) {
+ Log.v(TAG, "Unable to contact notification manager", ex);
+ }
+ return null;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (mWrapper == null) {
+ mWrapper = new INotificationListenerWrapper();
+ }
+ return mWrapper;
+ }
+
+ private class INotificationListenerWrapper extends INotificationListener.Stub {
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn) {
+ NotificationListenerService.this.onNotificationPosted(sbn);
+ }
+ @Override
+ public void onNotificationRemoved(StatusBarNotification sbn) {
+ NotificationListenerService.this.onNotificationRemoved(sbn);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.aidl b/core/java/android/service/notification/StatusBarNotification.aidl
index bd9e89c..ba81972 100644
--- a/core/java/com/android/internal/statusbar/StatusBarNotification.aidl
+++ b/core/java/android/service/notification/StatusBarNotification.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.statusbar;
+package android.service.notification;
parcelable StatusBarNotification;
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
new file mode 100644
index 0000000..19f8678
--- /dev/null
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -0,0 +1,244 @@
+/*
+ * 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.service.notification;
+
+import android.app.Notification;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+/**
+ * Class encapsulating a Notification. Sent by the NotificationManagerService to clients including
+ * the status bar and any {@link android.service.notification.NotificationListenerService}s.
+ */
+public class StatusBarNotification implements Parcelable {
+ private final String pkg;
+ private final int id;
+ private final String tag;
+
+ private final int uid;
+ private final String basePkg;
+ private final int initialPid;
+ // TODO: make this field private and move callers to an accessor that
+ // ensures sourceUser is applied.
+
+ private final Notification notification;
+ private final UserHandle user;
+ private final long postTime;
+
+ private final int score;
+
+ /** This is temporarily needed for the JB MR1 PDK.
+ * @hide */
+ @Deprecated
+ public StatusBarNotification(String pkg, int id, String tag, int uid, int initialPid, int score,
+ Notification notification) {
+ this(pkg, id, tag, uid, initialPid, score, notification, UserHandle.OWNER);
+ }
+
+ /** @hide */
+ public StatusBarNotification(String pkg, int id, String tag, int uid, int initialPid, int score,
+ Notification notification, UserHandle user) {
+ this(pkg, null, id, tag, uid, initialPid, score, notification, user);
+ }
+
+ /** @hide */
+ public StatusBarNotification(String pkg, String basePkg, int id, String tag, int uid,
+ int initialPid, int score, Notification notification, UserHandle user) {
+ this(pkg, basePkg, id, tag, uid, initialPid, score, notification, user,
+ System.currentTimeMillis());
+ }
+
+ public StatusBarNotification(String pkg, String basePkg, int id, String tag, int uid,
+ int initialPid, int score, Notification notification, UserHandle user,
+ long postTime) {
+ if (pkg == null) throw new NullPointerException();
+ if (notification == null) throw new NullPointerException();
+
+ this.pkg = pkg;
+ this.basePkg = pkg;
+ this.id = id;
+ this.tag = tag;
+ this.uid = uid;
+ this.initialPid = initialPid;
+ this.score = score;
+ this.notification = notification;
+ this.user = user;
+ this.notification.setUser(user);
+
+ this.postTime = postTime;
+ }
+
+ public StatusBarNotification(Parcel in) {
+ this.pkg = in.readString();
+ this.basePkg = in.readString();
+ this.id = in.readInt();
+ if (in.readInt() != 0) {
+ this.tag = in.readString();
+ } else {
+ this.tag = null;
+ }
+ this.uid = in.readInt();
+ this.initialPid = in.readInt();
+ this.score = in.readInt();
+ this.notification = new Notification(in);
+ this.user = UserHandle.readFromParcel(in);
+ this.notification.setUser(this.user);
+ this.postTime = in.readLong();
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(this.pkg);
+ out.writeString(this.basePkg);
+ out.writeInt(this.id);
+ if (this.tag != null) {
+ out.writeInt(1);
+ out.writeString(this.tag);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeInt(this.uid);
+ out.writeInt(this.initialPid);
+ out.writeInt(this.score);
+ this.notification.writeToParcel(out, flags);
+ user.writeToParcel(out, flags);
+
+ out.writeLong(this.postTime);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<StatusBarNotification> CREATOR
+ = new Parcelable.Creator<StatusBarNotification>()
+ {
+ public StatusBarNotification createFromParcel(Parcel parcel)
+ {
+ return new StatusBarNotification(parcel);
+ }
+
+ public StatusBarNotification[] newArray(int size)
+ {
+ return new StatusBarNotification[size];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public StatusBarNotification cloneLight() {
+ final Notification no = new Notification();
+ this.notification.cloneInto(no, false); // light copy
+ return new StatusBarNotification(this.pkg, this.basePkg,
+ this.id, this.tag, this.uid, this.initialPid,
+ this.score, no, this.user, this.postTime);
+ }
+
+ @Override
+ public StatusBarNotification clone() {
+ return new StatusBarNotification(this.pkg, this.basePkg,
+ this.id, this.tag, this.uid, this.initialPid,
+ this.score, this.notification.clone(), this.user, this.postTime);
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "StatusBarNotification(pkg=%s user=%s id=%d tag=%s score=%d: %s)",
+ this.pkg, this.user, this.id, this.tag,
+ this.score, this.notification);
+ }
+
+ /** Convenience method to check the notification's flags for
+ * {@link Notification#FLAG_ONGOING_EVENT}.
+ */
+ public boolean isOngoing() {
+ return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
+ }
+
+ /** Convenience method to check the notification's flags for
+ * either {@link Notification#FLAG_ONGOING_EVENT} or
+ * {@link Notification#FLAG_NO_CLEAR}.
+ */
+ public boolean isClearable() {
+ 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();
+ }
+
+ /** The package of the app that posted the notification. */
+ public String getPackageName() {
+ return pkg;
+ }
+
+ /** The id supplied to {@link android.app.NotificationManager#notify(int,Notification)}. */
+ public int getId() {
+ return id;
+ }
+
+ /** The tag supplied to {@link android.app.NotificationManager#notify(int,Notification)},
+ * or null if no tag was specified. */
+ public String getTag() {
+ return tag;
+ }
+
+ /** The notifying app's calling uid. @hide */
+ public int getUid() {
+ return uid;
+ }
+
+ /** The notifying app's base package. @hide */
+ public String getBasePkg() {
+ return basePkg;
+ }
+
+ /** @hide */
+ public int getInitialPid() {
+ return initialPid;
+ }
+
+ /** The {@link android.app.Notification} supplied to
+ * {@link android.app.NotificationManager#notify(int,Notification)}. */
+ public Notification getNotification() {
+ return notification;
+ }
+
+ /**
+ * The {@link android.os.UserHandle} for whom this notification is intended.
+ * @hide
+ */
+ public UserHandle getUser() {
+ return user;
+ }
+
+ /** The time (in {@link System#currentTimeMillis} time) the notification was posted,
+ * which may be different than {@link android.app.Notification#when}.
+ */
+ public long getPostTime() {
+ return postTime;
+ }
+
+ /** @hide */
+ public int getScore() {
+ return score;
+ }
+}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index d1b23e4..5db8168 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -84,7 +84,7 @@ public abstract class WallpaperService extends Service {
* tag.
*/
public static final String SERVICE_META_DATA = "android.service.wallpaper";
-
+
static final String TAG = "WallpaperService";
static final boolean DEBUG = false;
@@ -100,7 +100,6 @@ public abstract class WallpaperService extends Service {
private static final int MSG_WINDOW_MOVED = 10035;
private static final int MSG_TOUCH_EVENT = 10040;
- private Looper mCallbackLooper;
private final ArrayList<Engine> mActiveEngines
= new ArrayList<Engine>();
@@ -154,6 +153,7 @@ public abstract class WallpaperService extends Service {
int mCurWindowPrivateFlags = mWindowPrivateFlags;
final Rect mVisibleInsets = new Rect();
final Rect mWinFrame = new Rect();
+ final Rect mOverscanInsets = new Rect();
final Rect mContentInsets = new Rect();
final Configuration mConfiguration = new Configuration();
@@ -253,7 +253,7 @@ public abstract class WallpaperService extends Service {
final BaseIWindow mWindow = new BaseIWindow() {
@Override
- public void resized(Rect frame, Rect contentInsets,
+ public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
reportDraw ? 1 : 0);
@@ -628,7 +628,7 @@ public abstract class WallpaperService extends Service {
final int relayoutResult = mSession.relayout(
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
- View.VISIBLE, 0, mWinFrame, mContentInsets,
+ View.VISIBLE, 0, mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mConfiguration, mSurfaceHolder.mSurface);
if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface
@@ -762,7 +762,7 @@ public abstract class WallpaperService extends Service {
mWindowToken = wrapper.mWindowToken;
mSurfaceHolder.setSizeFromLayout();
mInitializing = true;
- mSession = WindowManagerGlobal.getWindowSession(getMainLooper());
+ mSession = WindowManagerGlobal.getWindowSession();
mWindow.setSession(mSession);
@@ -1099,13 +1099,14 @@ public abstract class WallpaperService extends Service {
mTarget = context;
}
+ @Override
public void attach(IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight) {
new IWallpaperEngineWrapper(mTarget, conn, windowToken,
windowType, isPreview, reqWidth, reqHeight);
}
}
-
+
@Override
public void onCreate() {
super.onCreate();
@@ -1128,20 +1129,7 @@ public abstract class WallpaperService extends Service {
public final IBinder onBind(Intent intent) {
return new IWallpaperServiceWrapper(this);
}
-
- /**
- * This allows subclasses to change the thread that most callbacks
- * occur on. Currently hidden because it is mostly needed for the
- * image wallpaper (which runs in the system process and doesn't want
- * to get stuck running on that seriously in use main thread). Not
- * exposed right now because the semantics of this are not totally
- * well defined and some callbacks can still happen on the main thread).
- * @hide
- */
- public void setCallbackLooper(Looper looper) {
- mCallbackLooper = looper;
- }
-
+
/**
* Must be implemented to return a new instance of the wallpaper's engine.
* Note that multiple instances may be active at the same time, such as
diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java
index 3e33e8e..ab8f82f 100644
--- a/core/java/android/speech/tts/FileSynthesisCallback.java
+++ b/core/java/android/speech/tts/FileSynthesisCallback.java
@@ -20,10 +20,12 @@ import android.os.FileUtils;
import android.util.Log;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
/**
* Speech synthesis request that writes the audio to a WAV file.
@@ -39,16 +41,19 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
private static final short WAV_FORMAT_PCM = 0x0001;
private final Object mStateLock = new Object();
- private final File mFileName;
+
private int mSampleRateInHz;
private int mAudioFormat;
private int mChannelCount;
- private RandomAccessFile mFile;
+
+ private FileChannel mFileChannel;
+
+ private boolean mStarted = false;
private boolean mStopped = false;
private boolean mDone = false;
- FileSynthesisCallback(File fileName) {
- mFileName = fileName;
+ FileSynthesisCallback(FileChannel fileChannel) {
+ mFileChannel = fileChannel;
}
@Override
@@ -63,54 +68,23 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
* Must be called while holding the monitor on {@link #mStateLock}.
*/
private void cleanUp() {
- closeFileAndWidenPermissions();
- if (mFile != null) {
- mFileName.delete();
- }
+ closeFile();
}
/**
* Must be called while holding the monitor on {@link #mStateLock}.
*/
- private void closeFileAndWidenPermissions() {
+ private void closeFile() {
try {
- if (mFile != null) {
- mFile.close();
- mFile = null;
+ if (mFileChannel != null) {
+ mFileChannel.close();
+ mFileChannel = null;
}
} catch (IOException ex) {
- Log.e(TAG, "Failed to close " + mFileName + ": " + ex);
- }
-
- try {
- // Make the written file readable and writeable by everyone.
- // This allows the app that requested synthesis to read the file.
- //
- // Note that the directory this file was written must have already
- // been world writeable in order it to have been
- // written to in the first place.
- FileUtils.setPermissions(mFileName.getAbsolutePath(), 0666, -1, -1); //-rw-rw-rw
- } catch (SecurityException se) {
- Log.e(TAG, "Security exception setting rw permissions on : " + mFileName);
- }
- }
-
- /**
- * 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;
- }
+ Log.e(TAG, "Failed to close output file descriptor", ex);
}
-
- return true;
}
-
@Override
public int getMaxBufferSize() {
return MAX_AUDIO_BUFFER_SIZE;
@@ -132,25 +106,20 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
}
- if (mFile != null) {
+ if (mStarted) {
cleanUp();
throw new IllegalArgumentException("FileSynthesisRequest.start() called twice");
}
-
- if (!maybeCleanupExistingFile(mFileName)) {
- return TextToSpeech.ERROR;
- }
-
+ mStarted = true;
mSampleRateInHz = sampleRateInHz;
mAudioFormat = audioFormat;
mChannelCount = channelCount;
+
try {
- mFile = new RandomAccessFile(mFileName, "rw");
- // Reserve space for WAV header
- mFile.write(new byte[WAV_HEADER_LENGTH]);
+ mFileChannel.write(ByteBuffer.allocate(WAV_HEADER_LENGTH));
return TextToSpeech.SUCCESS;
} catch (IOException ex) {
- Log.e(TAG, "Failed to open " + mFileName + ": " + ex);
+ Log.e(TAG, "Failed to write wav header to output file descriptor" + ex);
cleanUp();
return TextToSpeech.ERROR;
}
@@ -168,15 +137,15 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
}
- if (mFile == null) {
+ if (mFileChannel == null) {
Log.e(TAG, "File not open");
return TextToSpeech.ERROR;
}
try {
- mFile.write(buffer, offset, length);
+ mFileChannel.write(ByteBuffer.wrap(buffer, offset, length));
return TextToSpeech.SUCCESS;
} catch (IOException ex) {
- Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+ Log.e(TAG, "Failed to write to output file descriptor", ex);
cleanUp();
return TextToSpeech.ERROR;
}
@@ -197,21 +166,21 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
}
- if (mFile == null) {
+ if (mFileChannel == null) {
Log.e(TAG, "File not open");
return TextToSpeech.ERROR;
}
try {
// Write WAV header at start of file
- mFile.seek(0);
- int dataLength = (int) (mFile.length() - WAV_HEADER_LENGTH);
- mFile.write(
+ mFileChannel.position(0);
+ int dataLength = (int) (mFileChannel.size() - WAV_HEADER_LENGTH);
+ mFileChannel.write(
makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength));
- closeFileAndWidenPermissions();
+ closeFile();
mDone = true;
return TextToSpeech.SUCCESS;
} catch (IOException ex) {
- Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+ Log.e(TAG, "Failed to write to output file descriptor", ex);
cleanUp();
return TextToSpeech.ERROR;
}
@@ -226,7 +195,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
}
}
- private byte[] makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount,
+ private ByteBuffer makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount,
int dataLength) {
// TODO: is AudioFormat.ENCODING_DEFAULT always the same as ENCODING_PCM_16BIT?
int sampleSizeInBytes = (audioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2);
@@ -251,8 +220,9 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
header.putShort(bitsPerSample);
header.put(new byte[]{ 'd', 'a', 't', 'a' });
header.putInt(dataLength);
+ header.flip();
- return headerBuf;
+ return header;
}
}
diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl
index ab63187..b7bc70c 100644
--- a/core/java/android/speech/tts/ITextToSpeechService.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechService.aidl
@@ -18,6 +18,7 @@ package android.speech.tts;
import android.net.Uri;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
import android.speech.tts.ITextToSpeechCallback;
/**
@@ -44,11 +45,12 @@ interface ITextToSpeechService {
* @param callingInstance a binder representing the identity of the calling
* TextToSpeech object.
* @param text The text to synthesize.
- * @param filename The file to write the synthesized audio to.
+ * @param fileDescriptor The file descriptor to write the synthesized audio to. Has to be
+ writable.
* @param param Request parameters.
*/
- int synthesizeToFile(in IBinder callingInstance, in String text,
- in String filename, in Bundle params);
+ int synthesizeToFileDescriptor(in IBinder callingInstance, in String text,
+ in ParcelFileDescriptor fileDescriptor, in Bundle params);
/**
* Plays an existing audio resource.
@@ -97,7 +99,19 @@ interface ITextToSpeechService {
* be empty too.
*/
String[] getLanguage();
-
+
+ /**
+ * Returns a default TTS language, country and variant as set by the user.
+ *
+ * Can be called from multiple threads.
+ *
+ * @return A 3-element array, containing language (ISO 3-letter code),
+ * country (ISO 3-letter code) and variant used by the engine.
+ * The country and variant may be {@code ""}. If country is empty, then variant must
+ * be empty too.
+ */
+ String[] getClientDefaultLanguage();
+
/**
* Checks whether the engine supports a given language.
*
@@ -131,6 +145,8 @@ interface ITextToSpeechService {
/**
* Notifies the engine that it should load a speech synthesis language.
*
+ * @param caller a binder representing the identity of the calling
+ * TextToSpeech object.
* @param lang ISO-3 language code.
* @param country ISO-3 country code. May be empty or null.
* @param variant Language variant. May be empty or null.
@@ -141,13 +157,14 @@ interface ITextToSpeechService {
* {@link TextToSpeech#LANG_MISSING_DATA}
* {@link TextToSpeech#LANG_NOT_SUPPORTED}.
*/
- int loadLanguage(in String lang, in String country, in String variant);
+ int loadLanguage(in IBinder caller, in String lang, in String country, in String variant);
/**
* Sets the callback that will be notified when playback of utterance from the
* given app are completed.
*
- * @param callingApp Package name for the app whose utterance the callback will handle.
+ * @param caller Instance a binder representing the identity of the calling
+ * TextToSpeech object.
* @param cb The callback.
*/
void setCallback(in IBinder caller, ITextToSpeechCallback cb);
diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java
index d70c371..f98bb09 100644
--- a/core/java/android/speech/tts/SynthesisCallback.java
+++ b/core/java/android/speech/tts/SynthesisCallback.java
@@ -22,10 +22,11 @@ package android.speech.tts;
* {@link #start}, then {@link #audioAvailable} until all audio has been provided, then finally
* {@link #done}.
*
- *
* {@link #error} can be called at any stage in the synthesis process to
* indicate that an error has occurred, but if the call is made after a call
* to {@link #done}, it might be discarded.
+ *
+ * After {@link #start} been called, {@link #done} must be called regardless of errors.
*/
public interface SynthesisCallback {
/**
@@ -72,6 +73,8 @@ public interface SynthesisCallback {
* This method should only be called on the synthesis thread,
* while in {@link TextToSpeechService#onSynthesizeText}.
*
+ * This method has to be called if {@link #start} was called.
+ *
* @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
*/
public int done();
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 5e367cb..578a86e 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -24,19 +24,25 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.media.AudioManager;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.MissingResourceException;
import java.util.Set;
/**
@@ -139,7 +145,10 @@ public class TextToSpeech {
* Listener that will be called when the TTS service has
* completed synthesizing an utterance. This is only called if the utterance
* has an utterance ID (see {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}).
+ *
+ * @deprecated Use {@link UtteranceProgressListener} instead.
*/
+ @Deprecated
public interface OnUtteranceCompletedListener {
/**
* Called when an utterance has been synthesized.
@@ -235,19 +244,28 @@ public class TextToSpeech {
/**
* Indicates erroneous data when checking the installation status of the resources used by
* the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
+ *
+ * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
*/
+ @Deprecated
public static final int CHECK_VOICE_DATA_BAD_DATA = -1;
/**
* Indicates missing resources when checking the installation status of the resources used
* by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
+ *
+ * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
*/
+ @Deprecated
public static final int CHECK_VOICE_DATA_MISSING_DATA = -2;
/**
* Indicates missing storage volume when checking the installation status of the resources
* used by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
+ *
+ * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
*/
+ @Deprecated
public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3;
/**
@@ -283,9 +301,8 @@ public class TextToSpeech {
"android.speech.tts.engine.INSTALL_TTS_DATA";
/**
- * Broadcast Action: broadcast to signal the completion of the installation of
- * the data files used by the synthesis engine. Success or failure is indicated in the
- * {@link #EXTRA_TTS_DATA_INSTALLED} extra.
+ * Broadcast Action: broadcast to signal the change in the list of available
+ * languages or/and their features.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_TTS_DATA_INSTALLED =
@@ -298,20 +315,16 @@ public class TextToSpeech {
* return one of the following codes:
* {@link #CHECK_VOICE_DATA_PASS},
* {@link #CHECK_VOICE_DATA_FAIL},
- * {@link #CHECK_VOICE_DATA_BAD_DATA},
- * {@link #CHECK_VOICE_DATA_MISSING_DATA}, or
- * {@link #CHECK_VOICE_DATA_MISSING_VOLUME}.
* <p> Moreover, the data received in the activity result will contain the following
* fields:
* <ul>
- * <li>{@link #EXTRA_VOICE_DATA_ROOT_DIRECTORY} which
- * indicates the path to the location of the resource files,</li>
- * <li>{@link #EXTRA_VOICE_DATA_FILES} which contains
- * the list of all the resource files,</li>
- * <li>and {@link #EXTRA_VOICE_DATA_FILES_INFO} which
- * contains, for each resource file, the description of the language covered by
- * the file in the xxx-YYY format, where xxx is the 3-letter ISO language code,
- * and YYY is the 3-letter ISO country code.</li>
+ * <li>{@link #EXTRA_AVAILABLE_VOICES} which contains an ArrayList<String> of all the
+ * available voices. The format of each voice is: lang-COUNTRY-variant where COUNTRY and
+ * variant are optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").</li>
+ * <li>{@link #EXTRA_UNAVAILABLE_VOICES} which contains an ArrayList<String> of all the
+ * unavailable voices (ones that user can install). The format of each voice is:
+ * lang-COUNTRY-variant where COUNTRY and variant are optional (ie, "eng" or
+ * "eng-USA" or "eng-USA-FEMALE").</li>
* </ul>
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -319,37 +332,33 @@ public class TextToSpeech {
"android.speech.tts.engine.CHECK_TTS_DATA";
/**
- * Activity intent for getting some sample text to use for demonstrating TTS.
+ * Activity intent for getting some sample text to use for demonstrating TTS. Specific
+ * locale have to be requested by passing following extra parameters:
+ * <ul>
+ * <li>language</li>
+ * <li>country</li>
+ * <li>variant</li>
+ * </ul>
*
- * @hide This intent was used by engines written against the old API.
- * Not sure if it should be exposed.
+ * Upon completion, the activity result may contain the following fields:
+ * <ul>
+ * <li>{@link #EXTRA_SAMPLE_TEXT} which contains an String with sample text.</li>
+ * </ul>
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_GET_SAMPLE_TEXT =
"android.speech.tts.engine.GET_SAMPLE_TEXT";
- // extras for a TTS engine's check data activity
/**
- * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
- * the TextToSpeech engine specifies the path to its resources.
+ * Extra information received with the {@link #ACTION_GET_SAMPLE_TEXT} intent result where
+ * the TextToSpeech engine returns an String with sample text for requested voice
*/
- public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
+ public static final String EXTRA_SAMPLE_TEXT = "sampleText";
- /**
- * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
- * the TextToSpeech engine specifies the file names of its resources under the
- * resource path.
- */
- public static final String EXTRA_VOICE_DATA_FILES = "dataFiles";
-
- /**
- * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
- * the TextToSpeech engine specifies the locale associated with each resource file.
- */
- public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
+ // extras for a TTS engine's check data activity
/**
- * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
+ * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
* the TextToSpeech engine returns an ArrayList<String> of all the available voices.
* The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
* optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
@@ -357,7 +366,7 @@ public class TextToSpeech {
public static final String EXTRA_AVAILABLE_VOICES = "availableVoices";
/**
- * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
+ * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
* the TextToSpeech engine returns an ArrayList<String> of all the unavailable voices.
* The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
* optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
@@ -365,22 +374,63 @@ public class TextToSpeech {
public static final String EXTRA_UNAVAILABLE_VOICES = "unavailableVoices";
/**
+ * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
+ * the TextToSpeech engine specifies the path to its resources.
+ *
+ * It may be used by language packages to find out where to put their data.
+ *
+ * @deprecated TTS engine implementation detail, this information has no use for
+ * text-to-speech API client.
+ */
+ @Deprecated
+ public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
+
+ /**
+ * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
+ * the TextToSpeech engine specifies the file names of its resources under the
+ * resource path.
+ *
+ * @deprecated TTS engine implementation detail, this information has no use for
+ * text-to-speech API client.
+ */
+ @Deprecated
+ public static final String EXTRA_VOICE_DATA_FILES = "dataFiles";
+
+ /**
+ * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
+ * the TextToSpeech engine specifies the locale associated with each resource file.
+ *
+ * @deprecated TTS engine implementation detail, this information has no use for
+ * text-to-speech API client.
+ */
+ @Deprecated
+ public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
+
+ /**
* Extra information sent with the {@link #ACTION_CHECK_TTS_DATA} intent where the
* caller indicates to the TextToSpeech engine which specific sets of voice data to
* check for by sending an ArrayList<String> of the voices that are of interest.
* The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
* optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
+ *
+ * @deprecated Redundant functionality, checking for existence of specific sets of voice
+ * data can be done on client side.
*/
+ @Deprecated
public static final String EXTRA_CHECK_VOICE_DATA_FOR = "checkVoiceDataFor";
// extras for a TTS engine's data installation
/**
- * Extra information received with the {@link #ACTION_TTS_DATA_INSTALLED} intent.
+ * Extra information received with the {@link #ACTION_TTS_DATA_INSTALLED} intent result.
* It indicates whether the data files for the synthesis engine were successfully
* installed. The installation was initiated with the {@link #ACTION_INSTALL_TTS_DATA}
* intent. The possible values for this extra are
* {@link TextToSpeech#SUCCESS} and {@link TextToSpeech#ERROR}.
+ *
+ * @deprecated No longer in use. If client ise interested in information about what
+ * changed, is should send ACTION_CHECK_TTS_DATA intent to discover available voices.
*/
+ @Deprecated
public static final String EXTRA_TTS_DATA_INSTALLED = "dataInstalled";
// keys for the parameters passed with speak commands. Hidden keys are used internally
@@ -473,11 +523,16 @@ public class TextToSpeech {
* for a description of how feature keys work. If set and supported by the engine
* as per {@link TextToSpeech#getFeatures(Locale)}, the engine must synthesize
* text on-device (without making network requests).
+ *
+ * @see TextToSpeech#speak(String, int, java.util.HashMap)
+ * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)
+ * @see TextToSpeech#getFeatures(java.util.Locale)
*/
public static final String KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedTts";
}
private final Context mContext;
+ private Connection mConnectingServiceConnection;
private Connection mServiceConnection;
private OnInitListener mInitListener;
// Written from an unspecified application thread, read from
@@ -553,21 +608,24 @@ public class TextToSpeech {
initTts();
}
- private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method) {
- return runAction(action, errorResult, method, false);
+ private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method,
+ boolean onlyEstablishedConnection) {
+ return runAction(action, errorResult, method, false, onlyEstablishedConnection);
}
private <R> R runAction(Action<R> action, R errorResult, String method) {
- return runAction(action, errorResult, method, true);
+ return runAction(action, errorResult, method, true, true);
}
- private <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
+ private <R> R runAction(Action<R> action, R errorResult, String method,
+ boolean reconnect, boolean onlyEstablishedConnection) {
synchronized (mStartLock) {
if (mServiceConnection == null) {
Log.w(TAG, method + " failed: not bound to TTS engine");
return errorResult;
}
- return mServiceConnection.runAction(action, errorResult, method, reconnect);
+ return mServiceConnection.runAction(action, errorResult, method, reconnect,
+ onlyEstablishedConnection);
}
}
@@ -630,6 +688,7 @@ public class TextToSpeech {
return false;
} else {
Log.i(TAG, "Sucessfully bound to " + engine);
+ mConnectingServiceConnection = connection;
return true;
}
}
@@ -653,6 +712,16 @@ public class TextToSpeech {
* so the TextToSpeech engine can be cleanly stopped.
*/
public void shutdown() {
+ // Special case, we are asked to shutdown connection that did finalize its connection.
+ synchronized (mStartLock) {
+ if (mConnectingServiceConnection != null) {
+ mContext.unbindService(mConnectingServiceConnection);
+ mConnectingServiceConnection = null;
+ return;
+ }
+ }
+
+ // Post connection case
runActionNoReconnect(new Action<Void>() {
@Override
public Void run(ITextToSpeechService service) throws RemoteException {
@@ -670,7 +739,7 @@ public class TextToSpeech {
mCurrentEngine = null;
return null;
}
- }, null, "shutdown");
+ }, null, "shutdown", false);
}
/**
@@ -793,10 +862,16 @@ public class TextToSpeech {
}
/**
- * Speaks the string using the specified queuing strategy and speech
- * parameters.
+ * Speaks the string using the specified queuing strategy and speech parameters.
+ * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
+ * requests and then returns. The synthesis might not have finished (or even started!) at the
+ * time when this method returns. In order to reliably detect errors during synthesis,
+ * we recommend setting an utterance progress listener (see
+ * {@link #setOnUtteranceProgressListener}) and using the
+ * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
*
- * @param text The string of text to be spoken.
+ * @param text The string of text to be spoken. No longer than
+ * {@link #getMaxSpeechInputLength()} characters.
* @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
* @param params Parameters for the request. Can be null.
* Supported parameter names:
@@ -809,7 +884,7 @@ public class TextToSpeech {
* the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
* engine named "com.svox.pico" if it is being used.
*
- * @return {@link #ERROR} or {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the speak operation.
*/
public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
return runAction(new Action<Integer>() {
@@ -830,6 +905,12 @@ public class TextToSpeech {
* Plays the earcon using the specified queueing mode and parameters.
* The earcon must already have been added with {@link #addEarcon(String, String)} or
* {@link #addEarcon(String, String, int)}.
+ * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
+ * requests and then returns. The synthesis might not have finished (or even started!) at the
+ * time when this method returns. In order to reliably detect errors during synthesis,
+ * we recommend setting an utterance progress listener (see
+ * {@link #setOnUtteranceProgressListener}) and using the
+ * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
*
* @param earcon The earcon that should be played
* @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
@@ -842,7 +923,7 @@ public class TextToSpeech {
* the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
* engine named "com.svox.pico" if it is being used.
*
- * @return {@link #ERROR} or {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation.
*/
public int playEarcon(final String earcon, final int queueMode,
final HashMap<String, String> params) {
@@ -862,6 +943,12 @@ public class TextToSpeech {
/**
* Plays silence for the specified amount of time using the specified
* queue mode.
+ * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
+ * requests and then returns. The synthesis might not have finished (or even started!) at the
+ * time when this method returns. In order to reliably detect errors during synthesis,
+ * we recommend setting an utterance progress listener (see
+ * {@link #setOnUtteranceProgressListener}) and using the
+ * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
*
* @param durationInMs The duration of the silence.
* @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
@@ -873,7 +960,7 @@ public class TextToSpeech {
* the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
* engine named "com.svox.pico" if it is being used.
*
- * @return {@link #ERROR} or {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilence operation.
*/
public int playSilence(final long durationInMs, final int queueMode,
final HashMap<String, String> params) {
@@ -1005,6 +1092,24 @@ public class TextToSpeech {
}
/**
+ * Returns a Locale instance describing the language currently being used as the default
+ * Text-to-speech language.
+ *
+ * @return language, country (if any) and variant (if any) used by the client stored in a
+ * Locale instance, or {@code null} on error.
+ */
+ public Locale getDefaultLanguage() {
+ return runAction(new Action<Locale>() {
+ @Override
+ public Locale run(ITextToSpeechService service) throws RemoteException {
+ String[] defaultLanguage = service.getClientDefaultLanguage();
+
+ return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
+ }
+ }, null, "getDefaultLanguage");
+ }
+
+ /**
* Sets the text-to-speech language.
* The TTS engine will try to use the closest match to the specified
* language as represented by the Locale, but there is no guarantee that the exact same Locale
@@ -1024,14 +1129,29 @@ public class TextToSpeech {
if (loc == null) {
return LANG_NOT_SUPPORTED;
}
- String language = loc.getISO3Language();
- String country = loc.getISO3Country();
+ String language = null, country = null;
+ try {
+ language = loc.getISO3Language();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
+ return LANG_NOT_SUPPORTED;
+ }
+
+ try {
+ country = loc.getISO3Country();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
+ return LANG_NOT_SUPPORTED;
+ }
+
String variant = loc.getVariant();
+
// Check if the language, country, variant are available, and cache
// the available parts.
// Note that the language is not actually set here, instead it is cached so it
// will be associated with all upcoming utterances.
- int result = service.loadLanguage(language, country, variant);
+
+ int result = service.loadLanguage(getCallerIdentity(), language, country, variant);
if (result >= LANG_AVAILABLE){
if (result < LANG_COUNTRY_VAR_AVAILABLE) {
variant = "";
@@ -1049,21 +1169,30 @@ public class TextToSpeech {
}
/**
- * Returns a Locale instance describing the language currently being used by the TextToSpeech
- * engine.
+ * Returns a Locale instance describing the language currently being used for synthesis
+ * requests sent to the TextToSpeech engine.
+ *
+ * In Android 4.2 and before (API <= 17) this function returns the language that is currently
+ * being used by the TTS engine. That is the last language set by this or any other
+ * client by a {@link TextToSpeech#setLanguage} call to the same engine.
*
- * @return language, country (if any) and variant (if any) used by the engine stored in a Locale
- * instance, or {@code null} on error.
+ * In Android versions after 4.2 this function returns the language that is currently being
+ * used for the synthesis requests sent from this client. That is the last language set
+ * by a {@link TextToSpeech#setLanguage} call on this instance.
+ *
+ * @return language, country (if any) and variant (if any) used by the client stored in a
+ * Locale instance, or {@code null} on error.
*/
public Locale getLanguage() {
return runAction(new Action<Locale>() {
@Override
- public Locale run(ITextToSpeechService service) throws RemoteException {
- String[] locStrings = service.getLanguage();
- if (locStrings != null && locStrings.length == 3) {
- return new Locale(locStrings[0], locStrings[1], locStrings[2]);
- }
- return null;
+ public Locale run(ITextToSpeechService service) {
+ /* No service call, but we're accessing mParams, hence need for
+ wrapping it as an Action instance */
+ String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, "");
+ String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, "");
+ String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, "");
+ return new Locale(lang, country, variant);
}
}, null, "getLanguage");
}
@@ -1081,16 +1210,38 @@ public class TextToSpeech {
return runAction(new Action<Integer>() {
@Override
public Integer run(ITextToSpeechService service) throws RemoteException {
- return service.isLanguageAvailable(loc.getISO3Language(),
- loc.getISO3Country(), loc.getVariant());
+ String language = null, country = null;
+
+ try {
+ language = loc.getISO3Language();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
+ return LANG_NOT_SUPPORTED;
+ }
+
+ try {
+ country = loc.getISO3Country();
+ } catch (MissingResourceException e) {
+ Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
+ return LANG_NOT_SUPPORTED;
+ }
+
+ return service.isLanguageAvailable(language, country, loc.getVariant());
}
}, LANG_NOT_SUPPORTED, "isLanguageAvailable");
}
/**
* Synthesizes the given text to a file using the specified parameters.
+ * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
+ * requests and then returns. The synthesis might not have finished (or even started!) at the
+ * time when this method returns. In order to reliably detect errors during synthesis,
+ * we recommend setting an utterance progress listener (see
+ * {@link #setOnUtteranceProgressListener}) and using the
+ * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
*
- * @param text The text that should be synthesized
+ * @param text The text that should be synthesized. No longer than
+ * {@link #getMaxSpeechInputLength()} characters.
* @param params Parameters for the request. Can be null.
* Supported parameter names:
* {@link Engine#KEY_PARAM_UTTERANCE_ID}.
@@ -1101,15 +1252,36 @@ public class TextToSpeech {
* @param filename Absolute file filename to write the generated audio data to.It should be
* something like "/sdcard/myappsounds/mysound.wav".
*
- * @return {@link #ERROR} or {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation.
*/
public int synthesizeToFile(final String text, final HashMap<String, String> params,
final String filename) {
return runAction(new Action<Integer>() {
@Override
public Integer run(ITextToSpeechService service) throws RemoteException {
- return service.synthesizeToFile(getCallerIdentity(), text, filename,
- getParams(params));
+ ParcelFileDescriptor fileDescriptor;
+ int returnValue;
+ try {
+ File file = new File(filename);
+ if(file.exists() && !file.canWrite()) {
+ Log.e(TAG, "Can't write to " + filename);
+ return ERROR;
+ }
+ fileDescriptor = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_WRITE_ONLY |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE);
+ returnValue = service.synthesizeToFileDescriptor(getCallerIdentity(), text,
+ fileDescriptor, getParams(params));
+ fileDescriptor.close();
+ return returnValue;
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Opening file " + filename + " failed", e);
+ return ERROR;
+ } catch (IOException e) {
+ Log.e(TAG, "Closing file " + filename + " failed", e);
+ return ERROR;
+ }
}
}, ERROR, "synthesizeToFile");
}
@@ -1253,9 +1425,13 @@ public class TextToSpeech {
return mEnginesHelper.getEngines();
}
-
private class Connection implements ServiceConnection {
private ITextToSpeechService mService;
+
+ private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
+
+ private boolean mEstablished;
+
private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
@Override
public void onDone(String utteranceId) {
@@ -1282,23 +1458,66 @@ public class TextToSpeech {
}
};
+ private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
+ private final ComponentName mName;
+
+ public SetupConnectionAsyncTask(ComponentName name) {
+ mName = name;
+ }
+
+ @Override
+ protected Integer doInBackground(Void... params) {
+ synchronized(mStartLock) {
+ if (isCancelled()) {
+ return null;
+ }
+
+ try {
+ mService.setCallback(getCallerIdentity(), mCallback);
+ String[] defaultLanguage = mService.getClientDefaultLanguage();
+
+ mParams.putString(Engine.KEY_PARAM_LANGUAGE, defaultLanguage[0]);
+ mParams.putString(Engine.KEY_PARAM_COUNTRY, defaultLanguage[1]);
+ mParams.putString(Engine.KEY_PARAM_VARIANT, defaultLanguage[2]);
+
+ Log.i(TAG, "Set up connection to " + mName);
+ return SUCCESS;
+ } catch (RemoteException re) {
+ Log.e(TAG, "Error connecting to service, setCallback() failed");
+ return ERROR;
+ }
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ synchronized(mStartLock) {
+ if (mOnSetupConnectionAsyncTask == this) {
+ mOnSetupConnectionAsyncTask = null;
+ }
+ mEstablished = true;
+ dispatchOnInit(result);
+ }
+ }
+ }
+
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- Log.i(TAG, "Connected to " + name);
synchronized(mStartLock) {
- if (mServiceConnection != null) {
- // Disconnect any previous service connection
- mServiceConnection.disconnect();
+ mConnectingServiceConnection = null;
+
+ Log.i(TAG, "Connected to " + name);
+
+ if (mOnSetupConnectionAsyncTask != null) {
+ mOnSetupConnectionAsyncTask.cancel(false);
}
- mServiceConnection = this;
+
mService = ITextToSpeechService.Stub.asInterface(service);
- try {
- mService.setCallback(getCallerIdentity(), mCallback);
- dispatchOnInit(SUCCESS);
- } catch (RemoteException re) {
- Log.e(TAG, "Error connecting to service, setCallback() failed");
- dispatchOnInit(ERROR);
- }
+ mServiceConnection = Connection.this;
+
+ mEstablished = false;
+ mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
+ mOnSetupConnectionAsyncTask.execute();
}
}
@@ -1306,37 +1525,63 @@ public class TextToSpeech {
return mCallback;
}
- @Override
- public void onServiceDisconnected(ComponentName name) {
+ /**
+ * Clear connection related fields and cancel mOnServiceConnectedAsyncTask if set.
+ *
+ * @return true if we cancel mOnSetupConnectionAsyncTask in progress.
+ */
+ private boolean clearServiceConnection() {
synchronized(mStartLock) {
+ boolean result = false;
+ if (mOnSetupConnectionAsyncTask != null) {
+ result = mOnSetupConnectionAsyncTask.cancel(false);
+ mOnSetupConnectionAsyncTask = null;
+ }
+
mService = null;
// If this is the active connection, clear it
if (mServiceConnection == this) {
mServiceConnection = null;
}
+ return result;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(TAG, "Asked to disconnect from " + name);
+ if (clearServiceConnection()) {
+ /* We need to protect against a rare case where engine
+ * dies just after successful connection - and we process onServiceDisconnected
+ * before OnServiceConnectedAsyncTask.onPostExecute. onServiceDisconnected cancels
+ * OnServiceConnectedAsyncTask.onPostExecute and we don't call dispatchOnInit
+ * with ERROR as argument.
+ */
+ dispatchOnInit(ERROR);
}
}
public void disconnect() {
mContext.unbindService(this);
+ clearServiceConnection();
+ }
- synchronized (mStartLock) {
- mService = null;
- // If this is the active connection, clear it
- if (mServiceConnection == this) {
- mServiceConnection = null;
- }
-
- }
+ public boolean isEstablished() {
+ return mService != null && mEstablished;
}
- public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
+ public <R> R runAction(Action<R> action, R errorResult, String method,
+ boolean reconnect, boolean onlyEstablishedConnection) {
synchronized (mStartLock) {
try {
if (mService == null) {
Log.w(TAG, method + " failed: not connected to TTS engine");
return errorResult;
}
+ if (onlyEstablishedConnection && !isEstablished()) {
+ Log.w(TAG, method + " failed: TTS engine connection not fully set up");
+ return errorResult;
+ }
return action.run(mService);
} catch (RemoteException ex) {
Log.e(TAG, method + " failed", ex);
@@ -1394,4 +1639,13 @@ public class TextToSpeech {
}
+ /**
+ * Limit of length of input string passed to speak and synthesizeToFile.
+ *
+ * @see #speak
+ * @see #synthesizeToFile
+ */
+ public static int getMaxSpeechInputLength() {
+ return 4000;
+ }
}
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index d124e68..703dcff 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -26,6 +26,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.provider.Settings;
@@ -33,7 +34,8 @@ import android.speech.tts.TextToSpeech.Engine;
import android.text.TextUtils;
import android.util.Log;
-import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
@@ -74,7 +76,7 @@ public abstract class TextToSpeechService extends Service {
private static final boolean DBG = false;
private static final String TAG = "TextToSpeechService";
- private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
+
private static final String SYNTH_THREAD_NAME = "SynthThread";
private SynthHandler mSynthHandler;
@@ -129,6 +131,8 @@ public abstract class TextToSpeechService extends Service {
*
* Can be called on multiple threads.
*
+ * Its return values HAVE to be consistent with onLoadLanguage.
+ *
* @param lang ISO-3 language code.
* @param country ISO-3 country code. May be empty or null.
* @param variant Language variant. May be empty or null.
@@ -163,6 +167,8 @@ public abstract class TextToSpeechService extends Service {
* at some point in the future.
*
* Can be called on multiple threads.
+ * In <= Android 4.2 (<= API 17) can be called on main and service binder threads.
+ * In > Android 4.2 (> API 17) can be called on main and synthesis threads.
*
* @param lang ISO-3 language code.
* @param country ISO-3 country code. May be empty or null.
@@ -256,7 +262,6 @@ public abstract class TextToSpeechService extends Service {
}
private class SynthHandler extends Handler {
-
private SpeechItem mCurrentSpeechItem = null;
public SynthHandler(Looper looper) {
@@ -275,7 +280,7 @@ public abstract class TextToSpeechService extends Service {
private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
if (mCurrentSpeechItem != null &&
- mCurrentSpeechItem.getCallerIdentity() == callerIdentity) {
+ (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) {
SpeechItem current = mCurrentSpeechItem;
mCurrentSpeechItem = null;
return current;
@@ -296,7 +301,6 @@ public abstract class TextToSpeechService extends Service {
if (current != null) {
current.stop();
}
-
// The AudioPlaybackHandler will be destroyed by the caller.
}
@@ -306,8 +310,15 @@ public abstract class TextToSpeechService extends Service {
* Called on a service binder thread.
*/
public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
+ UtteranceProgressDispatcher utterenceProgress = null;
+ if (speechItem instanceof UtteranceProgressDispatcher) {
+ utterenceProgress = (UtteranceProgressDispatcher) speechItem;
+ }
+
if (!speechItem.isValid()) {
- speechItem.dispatchOnError();
+ if (utterenceProgress != null) {
+ utterenceProgress.dispatchOnError();
+ }
return TextToSpeech.ERROR;
}
@@ -325,6 +336,7 @@ public abstract class TextToSpeechService extends Service {
}
};
Message msg = Message.obtain(this, runnable);
+
// The obj is used to remove all callbacks from the given app in
// stopForApp(String).
//
@@ -334,7 +346,9 @@ public abstract class TextToSpeechService extends Service {
return TextToSpeech.SUCCESS;
} else {
Log.w(TAG, "SynthThread has quit");
- speechItem.dispatchOnError();
+ if (utterenceProgress != null) {
+ utterenceProgress.dispatchOnError();
+ }
return TextToSpeech.ERROR;
}
}
@@ -370,7 +384,7 @@ public abstract class TextToSpeechService extends Service {
}
public int stopAll() {
- // Stop the current speech item unconditionally.
+ // Stop the current speech item unconditionally .
SpeechItem current = setCurrentSpeechItem(null);
if (current != null) {
current.stop();
@@ -393,7 +407,7 @@ public abstract class TextToSpeechService extends Service {
/**
* An item in the synth thread queue.
*/
- private abstract class SpeechItem implements UtteranceProgressDispatcher {
+ private abstract class SpeechItem {
private final Object mCallerIdentity;
protected final Bundle mParams;
private final int mCallerUid;
@@ -412,6 +426,15 @@ public abstract class TextToSpeechService extends Service {
return mCallerIdentity;
}
+
+ public int getCallerUid() {
+ return mCallerUid;
+ }
+
+ public int getCallerPid() {
+ return mCallerPid;
+ }
+
/**
* Checker whether the item is valid. If this method returns false, the item should not
* be played.
@@ -436,6 +459,8 @@ public abstract class TextToSpeechService extends Service {
return playImpl();
}
+ protected abstract int playImpl();
+
/**
* Stops the speech item.
* Must not be called more than once.
@@ -452,6 +477,23 @@ public abstract class TextToSpeechService extends Service {
stopImpl();
}
+ protected abstract void stopImpl();
+
+ protected synchronized boolean isStopped() {
+ return mStopped;
+ }
+ }
+
+ /**
+ * An item in the synth thread queue that process utterance.
+ */
+ private abstract class UtteranceSpeechItem extends SpeechItem
+ implements UtteranceProgressDispatcher {
+
+ public UtteranceSpeechItem(Object caller, int callerUid, int callerPid, Bundle params) {
+ super(caller, callerUid, callerPid, params);
+ }
+
@Override
public void dispatchOnDone() {
final String utteranceId = getUtteranceId();
@@ -476,22 +518,6 @@ public abstract class TextToSpeechService extends Service {
}
}
- public int getCallerUid() {
- return mCallerUid;
- }
-
- public int getCallerPid() {
- return mCallerPid;
- }
-
- protected synchronized boolean isStopped() {
- return mStopped;
- }
-
- protected abstract int playImpl();
-
- protected abstract void stopImpl();
-
public int getStreamType() {
return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
}
@@ -519,9 +545,10 @@ public abstract class TextToSpeechService extends Service {
protected float getFloatParam(String key, float defaultValue) {
return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue);
}
+
}
- class SynthesisSpeechItem extends SpeechItem {
+ class SynthesisSpeechItem extends UtteranceSpeechItem {
// Never null.
private final String mText;
private final SynthesisRequest mSynthesisRequest;
@@ -552,7 +579,7 @@ public abstract class TextToSpeechService extends Service {
Log.e(TAG, "null synthesis text");
return false;
}
- if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH) {
+ if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) {
Log.w(TAG, "Text too long: " + mText.length() + " chars");
return false;
}
@@ -630,19 +657,18 @@ public abstract class TextToSpeechService extends Service {
}
}
- private class SynthesisToFileSpeechItem extends SynthesisSpeechItem {
- private final File mFile;
+ private class SynthesisToFileOutputStreamSpeechItem extends SynthesisSpeechItem {
+ private final FileOutputStream mFileOutputStream;
- public SynthesisToFileSpeechItem(Object callerIdentity, int callerUid, int callerPid,
- Bundle params, String text,
- File file) {
+ public SynthesisToFileOutputStreamSpeechItem(Object callerIdentity, int callerUid,
+ int callerPid, Bundle params, String text, FileOutputStream fileOutputStream) {
super(callerIdentity, callerUid, callerPid, params, text);
- mFile = file;
+ mFileOutputStream = fileOutputStream;
}
@Override
protected AbstractSynthesisCallback createSynthesisCallback() {
- return new FileSynthesisCallback(mFile);
+ return new FileSynthesisCallback(mFileOutputStream.getChannel());
}
@Override
@@ -654,11 +680,16 @@ public abstract class TextToSpeechService extends Service {
} else {
dispatchOnError();
}
+ try {
+ mFileOutputStream.close();
+ } catch(IOException e) {
+ Log.w(TAG, "Failed to close output file", e);
+ }
return status;
}
}
- private class AudioSpeechItem extends SpeechItem {
+ private class AudioSpeechItem extends UtteranceSpeechItem {
private final AudioPlaybackQueueItem mItem;
public AudioSpeechItem(Object callerIdentity, int callerUid, int callerPid,
Bundle params, Uri uri) {
@@ -684,7 +715,7 @@ public abstract class TextToSpeechService extends Service {
}
}
- private class SilenceSpeechItem extends SpeechItem {
+ private class SilenceSpeechItem extends UtteranceSpeechItem {
private final long mDuration;
public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
@@ -711,6 +742,41 @@ public abstract class TextToSpeechService extends Service {
}
}
+ private class LoadLanguageItem extends SpeechItem {
+ private final String mLanguage;
+ private final String mCountry;
+ private final String mVariant;
+
+ public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid,
+ Bundle params, String language, String country, String variant) {
+ super(callerIdentity, callerUid, callerPid, params);
+ mLanguage = language;
+ mCountry = country;
+ mVariant = variant;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ protected int playImpl() {
+ int result = TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
+ if (result == TextToSpeech.LANG_AVAILABLE ||
+ result == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
+ result == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
+ return TextToSpeech.SUCCESS;
+ }
+ return TextToSpeech.ERROR;
+ }
+
+ @Override
+ protected void stopImpl() {
+ // No-op
+ }
+ }
+
@Override
public IBinder onBind(Intent intent) {
if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
@@ -738,15 +804,21 @@ public abstract class TextToSpeechService extends Service {
}
@Override
- public int synthesizeToFile(IBinder caller, String text, String filename,
- Bundle params) {
- if (!checkNonNull(caller, text, filename, params)) {
+ public int synthesizeToFileDescriptor(IBinder caller, String text, ParcelFileDescriptor
+ fileDescriptor, Bundle params) {
+ if (!checkNonNull(caller, text, fileDescriptor, params)) {
return TextToSpeech.ERROR;
}
- File file = new File(filename);
- SpeechItem item = new SynthesisToFileSpeechItem(caller, Binder.getCallingUid(),
- Binder.getCallingPid(), params, text, file);
+ // In test env, ParcelFileDescriptor instance may be EXACTLY the same
+ // one that is used by client. And it will be closed by a client, thus
+ // preventing us from writing anything to it.
+ final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd(
+ fileDescriptor.detachFd());
+
+ SpeechItem item = new SynthesisToFileOutputStreamSpeechItem(caller,
+ Binder.getCallingUid(), Binder.getCallingPid(), params, text,
+ new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
}
@@ -791,6 +863,11 @@ public abstract class TextToSpeechService extends Service {
return onGetLanguage();
}
+ @Override
+ public String[] getClientDefaultLanguage() {
+ return getSettingsLocale();
+ }
+
/*
* If defaults are enforced, then no language is "available" except
* perhaps the default language selected by the user.
@@ -822,12 +899,25 @@ public abstract class TextToSpeechService extends Service {
* are enforced.
*/
@Override
- public int loadLanguage(String lang, String country, String variant) {
+ public int loadLanguage(IBinder caller, String lang, String country, String variant) {
if (!checkNonNull(lang)) {
return TextToSpeech.ERROR;
}
+ int retVal = onIsLanguageAvailable(lang, country, variant);
- return onLoadLanguage(lang, country, variant);
+ if (retVal == TextToSpeech.LANG_AVAILABLE ||
+ retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
+ retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
+
+ SpeechItem item = new LoadLanguageItem(caller, Binder.getCallingUid(),
+ Binder.getCallingPid(), null, lang, country, variant);
+
+ if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item) !=
+ TextToSpeech.SUCCESS) {
+ return TextToSpeech.ERROR;
+ }
+ }
+ return retVal;
}
@Override
diff --git a/core/java/android/test/AndroidTestCase.java b/core/java/android/test/AndroidTestCase.java
index 0c8cbe6..0635559 100644
--- a/core/java/android/test/AndroidTestCase.java
+++ b/core/java/android/test/AndroidTestCase.java
@@ -20,9 +20,11 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+
import junit.framework.TestCase;
import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
/**
* Extend this if you need to access Resources or other things that depend on Activity Context.
@@ -152,11 +154,11 @@ public class AndroidTestCase extends TestCase {
* @throws IllegalAccessException
*/
protected void scrubClass(final Class<?> testCaseClass)
- throws IllegalAccessException {
+ throws IllegalAccessException {
final Field[] fields = getClass().getDeclaredFields();
for (Field field : fields) {
- final Class<?> fieldClass = field.getDeclaringClass();
- if (testCaseClass.isAssignableFrom(fieldClass) && !field.getType().isPrimitive()) {
+ if (!field.getType().isPrimitive() &&
+ !Modifier.isStatic(field.getModifiers())) {
try {
field.setAccessible(true);
field.set(this, null);
@@ -170,6 +172,4 @@ public class AndroidTestCase extends TestCase {
}
}
}
-
-
}
diff --git a/core/java/android/text/BidiFormatter.java b/core/java/android/text/BidiFormatter.java
new file mode 100644
index 0000000..2a2589a
--- /dev/null
+++ b/core/java/android/text/BidiFormatter.java
@@ -0,0 +1,890 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.view.View;
+
+import static android.text.TextDirectionHeuristics.FIRSTSTRONG_LTR;
+
+import java.util.Locale;
+
+/**
+ * Utility class for formatting text for display in a potentially opposite-directionality context
+ * without garbling. The directionality of the context is set at formatter creation and the
+ * directionality of the text can be either estimated or passed in when known.
+ *
+ * <p>To support versions lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * you can use the support library's {@link android.support.v4.text.BidiFormatter} class.
+ *
+ * <p>These APIs provides the following functionality:
+ * <p>
+ * 1. Bidi Wrapping
+ * When text in one language is mixed into a document in another, opposite-directionality language,
+ * e.g. when an English business name is embedded in some Hebrew text, both the inserted string
+ * and the text surrounding it may be displayed incorrectly unless the inserted string is explicitly
+ * separated from the surrounding text in a "wrapper" that:
+ * <p>
+ * - Declares its directionality so that the string is displayed correctly. This can be done in
+ * Unicode bidi formatting codes by {@link #unicodeWrap} and similar methods.
+ * <p>
+ * - Isolates the string's directionality, so it does not unduly affect the surrounding content.
+ * Currently, this can only be done using invisible Unicode characters of the same direction as
+ * the context (LRM or RLM) in addition to the directionality declaration above, thus "resetting"
+ * the directionality to that of the context. The "reset" may need to be done at both ends of the
+ * string. Without "reset" after the string, the string will "stick" to a number or logically
+ * separate opposite-direction text that happens to follow it in-line (even if separated by
+ * neutral content like spaces and punctuation). Without "reset" before the string, the same can
+ * happen there, but only with more opposite-direction text, not a number. One approach is to
+ * "reset" the direction only after each string, on the theory that if the preceding opposite-
+ * direction text is itself bidi-wrapped, the "reset" after it will prevent the sticking. (Doing
+ * the "reset" only before each string definitely does not work because we do not want to require
+ * bidi-wrapping numbers, and a bidi-wrapped opposite-direction string could be followed by a
+ * number.) Still, the safest policy is to do the "reset" on both ends of each string, since RTL
+ * message translations often contain untranslated Latin-script brand names and technical terms,
+ * and one of these can be followed by a bidi-wrapped inserted value. On the other hand, when one
+ * has such a message, it is best to do the "reset" manually in the message translation itself,
+ * since the message's opposite-direction text could be followed by an inserted number, which we
+ * would not bidi-wrap anyway. Thus, "reset" only after the string is the current default. In an
+ * alternative to "reset", recent additions to the HTML, CSS, and Unicode standards allow the
+ * isolation to be part of the directionality declaration. This form of isolation is better than
+ * "reset" because it takes less space, does not require knowing the context directionality, has a
+ * gentler effect than "reset", and protects both ends of the string. However, we do not yet allow
+ * using it because required platforms do not yet support it.
+ * <p>
+ * Providing these wrapping services is the basic purpose of the bidi formatter.
+ * <p>
+ * 2. Directionality estimation
+ * How does one know whether a string about to be inserted into surrounding text has the same
+ * directionality? Well, in many cases, one knows that this must be the case when writing the code
+ * doing the insertion, e.g. when a localized message is inserted into a localized page. In such
+ * cases there is no need to involve the bidi formatter at all. In some other cases, it need not be
+ * the same as the context, but is either constant (e.g. urls are always LTR) or otherwise known.
+ * In the remaining cases, e.g. when the string is user-entered or comes from a database, the
+ * language of the string (and thus its directionality) is not known a priori, and must be
+ * estimated at run-time. The bidi formatter can do this automatically using the default
+ * first-strong estimation algorithm. It can also be configured to use a custom directionality
+ * estimation object.
+ */
+public final class BidiFormatter {
+
+ /**
+ * The default text direction heuristic.
+ */
+ private static TextDirectionHeuristic DEFAULT_TEXT_DIRECTION_HEURISTIC = FIRSTSTRONG_LTR;
+
+ /**
+ * Unicode "Left-To-Right Embedding" (LRE) character.
+ */
+ private static final char LRE = '\u202A';
+
+ /**
+ * Unicode "Right-To-Left Embedding" (RLE) character.
+ */
+ private static final char RLE = '\u202B';
+
+ /**
+ * Unicode "Pop Directional Formatting" (PDF) character.
+ */
+ private static final char PDF = '\u202C';
+
+ /**
+ * Unicode "Left-To-Right Mark" (LRM) character.
+ */
+ private static final char LRM = '\u200E';
+
+ /*
+ * Unicode "Right-To-Left Mark" (RLM) character.
+ */
+ private static final char RLM = '\u200F';
+
+ /*
+ * String representation of LRM
+ */
+ private static final String LRM_STRING = Character.toString(LRM);
+
+ /*
+ * String representation of RLM
+ */
+ private static final String RLM_STRING = Character.toString(RLM);
+
+ /**
+ * Empty string constant.
+ */
+ private static final String EMPTY_STRING = "";
+
+ /**
+ * A class for building a BidiFormatter with non-default options.
+ */
+ public static final class Builder {
+ private boolean mIsRtlContext;
+ private int mFlags;
+ private TextDirectionHeuristic mTextDirectionHeuristic;
+
+ /**
+ * Constructor.
+ *
+ */
+ public Builder() {
+ initialize(isRtlLocale(Locale.getDefault()));
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param rtlContext Whether the context directionality is RTL.
+ */
+ public Builder(boolean rtlContext) {
+ initialize(rtlContext);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param locale The context locale.
+ */
+ public Builder(Locale locale) {
+ initialize(isRtlLocale(locale));
+ }
+
+ /**
+ * Initializes the builder with the given context directionality and default options.
+ *
+ * @param isRtlContext Whether the context is RTL or not.
+ */
+ private void initialize(boolean isRtlContext) {
+ mIsRtlContext = isRtlContext;
+ mTextDirectionHeuristic = DEFAULT_TEXT_DIRECTION_HEURISTIC;
+ mFlags = DEFAULT_FLAGS;
+ }
+
+ /**
+ * Specifies whether the BidiFormatter to be built should also "reset" directionality before
+ * a string being bidi-wrapped, not just after it. The default is false.
+ */
+ public Builder stereoReset(boolean stereoReset) {
+ if (stereoReset) {
+ mFlags |= FLAG_STEREO_RESET;
+ } else {
+ mFlags &= ~FLAG_STEREO_RESET;
+ }
+ return this;
+ }
+
+ /**
+ * Specifies the default directionality estimation algorithm to be used by the BidiFormatter.
+ * By default, uses the first-strong heuristic.
+ *
+ * @param heuristic the {@code TextDirectionHeuristic} to use.
+ * @return the builder itself.
+ */
+ public Builder setTextDirectionHeuristic(TextDirectionHeuristic heuristic) {
+ mTextDirectionHeuristic = heuristic;
+ return this;
+ }
+
+ private static BidiFormatter getDefaultInstanceFromContext(boolean isRtlContext) {
+ return isRtlContext ? DEFAULT_RTL_INSTANCE : DEFAULT_LTR_INSTANCE;
+ }
+
+ /**
+ * @return A BidiFormatter with the specified options.
+ */
+ public BidiFormatter build() {
+ if (mFlags == DEFAULT_FLAGS &&
+ mTextDirectionHeuristic == DEFAULT_TEXT_DIRECTION_HEURISTIC) {
+ return getDefaultInstanceFromContext(mIsRtlContext);
+ }
+ return new BidiFormatter(mIsRtlContext, mFlags, mTextDirectionHeuristic);
+ }
+ }
+
+ //
+ private static final int FLAG_STEREO_RESET = 2;
+ private static final int DEFAULT_FLAGS = FLAG_STEREO_RESET;
+
+ private static final BidiFormatter DEFAULT_LTR_INSTANCE = new BidiFormatter(
+ false /* LTR context */,
+ DEFAULT_FLAGS,
+ DEFAULT_TEXT_DIRECTION_HEURISTIC);
+
+ private static final BidiFormatter DEFAULT_RTL_INSTANCE = new BidiFormatter(
+ true /* RTL context */,
+ DEFAULT_FLAGS,
+ DEFAULT_TEXT_DIRECTION_HEURISTIC);
+
+ private final boolean mIsRtlContext;
+ private final int mFlags;
+ private final TextDirectionHeuristic mDefaultTextDirectionHeuristic;
+
+ /**
+ * Factory for creating an instance of BidiFormatter for the default locale directionality.
+ *
+ */
+ public static BidiFormatter getInstance() {
+ return new Builder().build();
+ }
+
+ /**
+ * Factory for creating an instance of BidiFormatter given the context directionality.
+ *
+ * @param rtlContext Whether the context directionality is RTL.
+ */
+ public static BidiFormatter getInstance(boolean rtlContext) {
+ return new Builder(rtlContext).build();
+ }
+
+ /**
+ * Factory for creating an instance of BidiFormatter given the context locale.
+ *
+ * @param locale The context locale.
+ */
+ public static BidiFormatter getInstance(Locale locale) {
+ return new Builder(locale).build();
+ }
+
+ /**
+ * @param isRtlContext Whether the context directionality is RTL or not.
+ * @param flags The option flags.
+ * @param heuristic The default text direction heuristic.
+ */
+ private BidiFormatter(boolean isRtlContext, int flags, TextDirectionHeuristic heuristic) {
+ mIsRtlContext = isRtlContext;
+ mFlags = flags;
+ mDefaultTextDirectionHeuristic = heuristic;
+ }
+
+ /**
+ * @return Whether the context directionality is RTL
+ */
+ public boolean isRtlContext() {
+ return mIsRtlContext;
+ }
+
+ /**
+ * @return Whether directionality "reset" should also be done before a string being
+ * bidi-wrapped, not just after it.
+ */
+ public boolean getStereoReset() {
+ return (mFlags & FLAG_STEREO_RESET) != 0;
+ }
+
+ /**
+ * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the
+ * overall or the exit directionality of a given string is opposite to the context directionality.
+ * Putting this after the string (including its directionality declaration wrapping) prevents it
+ * from "sticking" to other opposite-directionality text or a number appearing after it inline
+ * with only neutral content in between. Otherwise returns the empty string. While the exit
+ * directionality is determined by scanning the end of the string, the overall directionality is
+ * given explicitly by a heuristic to estimate the {@code str}'s directionality.
+ *
+ * @param str String after which the mark may need to appear.
+ * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
+ * directionality.
+ * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
+ * else, the empty string.
+ *
+ * @hide
+ */
+ public String markAfter(String str, TextDirectionHeuristic heuristic) {
+ final boolean isRtl = heuristic.isRtl(str, 0, str.length());
+ // getExitDir() is called only if needed (short-circuit).
+ if (!mIsRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) {
+ return LRM_STRING;
+ }
+ if (mIsRtlContext && (!isRtl || getExitDir(str) == DIR_LTR)) {
+ return RLM_STRING;
+ }
+ return EMPTY_STRING;
+ }
+
+ /**
+ * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the
+ * overall or the entry directionality of a given string is opposite to the context
+ * directionality. Putting this before the string (including its directionality declaration
+ * wrapping) prevents it from "sticking" to other opposite-directionality text appearing before
+ * it inline with only neutral content in between. Otherwise returns the empty string. While the
+ * entry directionality is determined by scanning the beginning of the string, the overall
+ * directionality is given explicitly by a heuristic to estimate the {@code str}'s directionality.
+ *
+ * @param str String before which the mark may need to appear.
+ * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
+ * directionality.
+ * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
+ * else, the empty string.
+ *
+ * @hide
+ */
+ public String markBefore(String str, TextDirectionHeuristic heuristic) {
+ final boolean isRtl = heuristic.isRtl(str, 0, str.length());
+ // getEntryDir() is called only if needed (short-circuit).
+ if (!mIsRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) {
+ return LRM_STRING;
+ }
+ if (mIsRtlContext && (!isRtl || getEntryDir(str) == DIR_LTR)) {
+ return RLM_STRING;
+ }
+ return EMPTY_STRING;
+ }
+
+ /**
+ * Estimates the directionality of a string using the default text direction heuristic.
+ *
+ * @param str String whose directionality is to be estimated.
+ * @return true if {@code str}'s estimated overall directionality is RTL. Otherwise returns
+ * false.
+ */
+ public boolean isRtl(String str) {
+ return mDefaultTextDirectionHeuristic.isRtl(str, 0, str.length());
+ }
+
+ /**
+ * Formats a string of given directionality for use in plain-text output of the context
+ * directionality, so an opposite-directionality string is neither garbled nor garbles its
+ * surroundings. This makes use of Unicode bidi formatting characters.
+ * <p>
+ * The algorithm: In case the given directionality doesn't match the context directionality, wraps
+ * the string with Unicode bidi formatting characters: RLE+{@code str}+PDF for RTL text, or
+ * LRE+{@code str}+PDF for LTR text.
+ * <p>
+ * If {@code isolate}, directionally isolates the string so that it does not garble its
+ * surroundings. Currently, this is done by "resetting" the directionality after the string by
+ * appending a trailing Unicode bidi mark matching the context directionality (LRM or RLM) when
+ * either the overall directionality or the exit directionality of the string is opposite to that
+ * of the context. If the formatter was built using {@link Builder#stereoReset(boolean)} and
+ * passing "true" as an argument, also prepends a Unicode bidi mark matching the context
+ * directionality when either the overall directionality or the entry directionality of the
+ * string is opposite to that of the context. Note that as opposed to the overall
+ * directionality, the entry and exit directionalities are determined from the string itself.
+ * <p>
+ * Does *not* do HTML-escaping.
+ *
+ * @param str The input string.
+ * @param heuristic The algorithm to be used to estimate the string's overall direction.
+ * See {@link TextDirectionHeuristics} for pre-defined heuristics.
+ * @param isolate Whether to directionally isolate the string to prevent it from garbling the
+ * content around it
+ * @return Input string after applying the above processing.
+ */
+ public String unicodeWrap(String str, TextDirectionHeuristic heuristic, boolean isolate) {
+ final boolean isRtl = heuristic.isRtl(str, 0, str.length());
+ StringBuilder result = new StringBuilder();
+ if (getStereoReset() && isolate) {
+ result.append(markBefore(str,
+ isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
+ }
+ if (isRtl != mIsRtlContext) {
+ result.append(isRtl ? RLE : LRE);
+ result.append(str);
+ result.append(PDF);
+ } else {
+ result.append(str);
+ }
+ if (isolate) {
+ result.append(markAfter(str,
+ isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
+ }
+ return result.toString();
+ }
+
+ /**
+ * Operates like {@link #unicodeWrap(String, TextDirectionHeuristic, boolean)}, but assumes
+ * {@code isolate} is true.
+ *
+ * @param str The input string.
+ * @param heuristic The algorithm to be used to estimate the string's overall direction.
+ * See {@link TextDirectionHeuristics} for pre-defined heuristics.
+ * @return Input string after applying the above processing.
+ */
+ public String unicodeWrap(String str, TextDirectionHeuristic heuristic) {
+ return unicodeWrap(str, heuristic, true /* isolate */);
+ }
+
+ /**
+ * Operates like {@link #unicodeWrap(String, TextDirectionHeuristic, boolean)}, but uses the
+ * formatter's default direction estimation algorithm.
+ *
+ * @param str The input string.
+ * @param isolate Whether to directionally isolate the string to prevent it from garbling the
+ * content around it
+ * @return Input string after applying the above processing.
+ */
+ public String unicodeWrap(String str, boolean isolate) {
+ return unicodeWrap(str, mDefaultTextDirectionHeuristic, isolate);
+ }
+
+ /**
+ * Operates like {@link #unicodeWrap(String, TextDirectionHeuristic, boolean)}, but uses the
+ * formatter's default direction estimation algorithm and assumes {@code isolate} is true.
+ *
+ * @param str The input string.
+ * @return Input string after applying the above processing.
+ */
+ public String unicodeWrap(String str) {
+ return unicodeWrap(str, mDefaultTextDirectionHeuristic, true /* isolate */);
+ }
+
+ /**
+ * Helper method to return true if the Locale directionality is RTL.
+ *
+ * @param locale The Locale whose directionality will be checked to be RTL or LTR
+ * @return true if the {@code locale} directionality is RTL. False otherwise.
+ */
+ private static boolean isRtlLocale(Locale locale) {
+ return (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL);
+ }
+
+ /**
+ * Enum for directionality type.
+ */
+ private static final int DIR_LTR = -1;
+ private static final int DIR_UNKNOWN = 0;
+ private static final int DIR_RTL = +1;
+
+ /**
+ * Returns the directionality of the last character with strong directionality in the string, or
+ * DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards from the end of
+ * the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its matching PDF as a
+ * strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results are undefined for a
+ * string containing unbalanced LRE/RLE/LRO/RLO/PDF characters. The intended use is to check
+ * whether a logically separate item that starts with a number or a character of the string's
+ * exit directionality and follows this string inline (not counting any neutral characters in
+ * between) would "stick" to it in an opposite-directionality context, thus being displayed in
+ * an incorrect position. An LRM or RLM character (the one of the context's directionality)
+ * between the two will prevent such sticking.
+ *
+ * @param str the string to check.
+ */
+ private static int getExitDir(String str) {
+ return new DirectionalityEstimator(str, false /* isHtml */).getExitDir();
+ }
+
+ /**
+ * Returns the directionality of the first character with strong directionality in the string,
+ * or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an
+ * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL after
+ * RLE/RLO. The results are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF
+ * characters. The intended use is to check whether a logically separate item that ends with a
+ * character of the string's entry directionality and precedes the string inline (not counting
+ * any neutral characters in between) would "stick" to it in an opposite-directionality context,
+ * thus being displayed in an incorrect position. An LRM or RLM character (the one of the
+ * context's directionality) between the two will prevent such sticking.
+ *
+ * @param str the string to check.
+ */
+ private static int getEntryDir(String str) {
+ return new DirectionalityEstimator(str, false /* isHtml */).getEntryDir();
+ }
+
+ /**
+ * An object that estimates the directionality of a given string by various methods.
+ *
+ */
+ private static class DirectionalityEstimator {
+
+ // Internal static variables and constants.
+
+ /**
+ * Size of the bidi character class cache. The results of the Character.getDirectionality()
+ * calls on the lowest DIR_TYPE_CACHE_SIZE codepoints are kept in an array for speed.
+ * The 0x700 value is designed to leave all the European and Near Eastern languages in the
+ * cache. It can be reduced to 0x180, restricting the cache to the Western European
+ * languages.
+ */
+ private static final int DIR_TYPE_CACHE_SIZE = 0x700;
+
+ /**
+ * The bidi character class cache.
+ */
+ private static final byte DIR_TYPE_CACHE[];
+
+ static {
+ DIR_TYPE_CACHE = new byte[DIR_TYPE_CACHE_SIZE];
+ for (int i = 0; i < DIR_TYPE_CACHE_SIZE; i++) {
+ DIR_TYPE_CACHE[i] = Character.getDirectionality(i);
+ }
+ }
+
+ // Internal instance variables.
+
+ /**
+ * The text to be scanned.
+ */
+ private final String text;
+
+ /**
+ * Whether the text to be scanned is to be treated as HTML, i.e. skipping over tags and
+ * entities when looking for the next / preceding dir type.
+ */
+ private final boolean isHtml;
+
+ /**
+ * The length of the text in chars.
+ */
+ private final int length;
+
+ /**
+ * The current position in the text.
+ */
+ private int charIndex;
+
+ /**
+ * The char encountered by the last dirTypeForward or dirTypeBackward call. If it
+ * encountered a supplementary codepoint, this contains a char that is not a valid
+ * codepoint. This is ok, because this member is only used to detect some well-known ASCII
+ * syntax, e.g. "http://" and the beginning of an HTML tag or entity.
+ */
+ private char lastChar;
+
+ /**
+ * Constructor.
+ *
+ * @param text The string to scan.
+ * @param isHtml Whether the text to be scanned is to be treated as HTML, i.e. skipping over
+ * tags and entities.
+ */
+ DirectionalityEstimator(String text, boolean isHtml) {
+ this.text = text;
+ this.isHtml = isHtml;
+ length = text.length();
+ }
+
+ /**
+ * Returns the directionality of the first character with strong directionality in the
+ * string, or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an
+ * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL
+ * after RLE/RLO. The results are undefined for a string containing unbalanced
+ * LRE/RLE/LRO/RLO/PDF characters.
+ */
+ int getEntryDir() {
+ // The reason for this method name, as opposed to getFirstStrongDir(), is that
+ // "first strong" is a commonly used description of Unicode's estimation algorithm,
+ // but the two must treat formatting characters quite differently. Thus, we are staying
+ // away from both "first" and "last" in these method names to avoid confusion.
+ charIndex = 0;
+ int embeddingLevel = 0;
+ int embeddingLevelDir = DIR_UNKNOWN;
+ int firstNonEmptyEmbeddingLevel = 0;
+ while (charIndex < length && firstNonEmptyEmbeddingLevel == 0) {
+ switch (dirTypeForward()) {
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
+ ++embeddingLevel;
+ embeddingLevelDir = DIR_LTR;
+ break;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
+ ++embeddingLevel;
+ embeddingLevelDir = DIR_RTL;
+ break;
+ case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
+ --embeddingLevel;
+ // To restore embeddingLevelDir to its previous value, we would need a
+ // stack, which we want to avoid. Thus, at this point we do not know the
+ // current embedding's directionality.
+ embeddingLevelDir = DIR_UNKNOWN;
+ break;
+ case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
+ break;
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
+ if (embeddingLevel == 0) {
+ return DIR_LTR;
+ }
+ firstNonEmptyEmbeddingLevel = embeddingLevel;
+ break;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+ if (embeddingLevel == 0) {
+ return DIR_RTL;
+ }
+ firstNonEmptyEmbeddingLevel = embeddingLevel;
+ break;
+ default:
+ firstNonEmptyEmbeddingLevel = embeddingLevel;
+ break;
+ }
+ }
+
+ // We have either found a non-empty embedding or scanned the entire string finding
+ // neither a non-empty embedding nor a strong character outside of an embedding.
+ if (firstNonEmptyEmbeddingLevel == 0) {
+ // We have not found a non-empty embedding. Thus, the string contains neither a
+ // non-empty embedding nor a strong character outside of an embedding.
+ return DIR_UNKNOWN;
+ }
+
+ // We have found a non-empty embedding.
+ if (embeddingLevelDir != DIR_UNKNOWN) {
+ // We know the directionality of the non-empty embedding.
+ return embeddingLevelDir;
+ }
+
+ // We do not remember the directionality of the non-empty embedding we found. So, we go
+ // backwards to find the start of the non-empty embedding and get its directionality.
+ while (charIndex > 0) {
+ switch (dirTypeBackward()) {
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
+ if (firstNonEmptyEmbeddingLevel == embeddingLevel) {
+ return DIR_LTR;
+ }
+ --embeddingLevel;
+ break;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
+ if (firstNonEmptyEmbeddingLevel == embeddingLevel) {
+ return DIR_RTL;
+ }
+ --embeddingLevel;
+ break;
+ case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
+ ++embeddingLevel;
+ break;
+ }
+ }
+ // We should never get here.
+ return DIR_UNKNOWN;
+ }
+
+ /**
+ * Returns the directionality of the last character with strong directionality in the
+ * string, or DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards
+ * from the end of the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its
+ * matching PDF as a strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results
+ * are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF characters.
+ */
+ int getExitDir() {
+ // The reason for this method name, as opposed to getLastStrongDir(), is that "last
+ // strong" sounds like the exact opposite of "first strong", which is a commonly used
+ // description of Unicode's estimation algorithm (getUnicodeDir() above), but the two
+ // must treat formatting characters quite differently. Thus, we are staying away from
+ // both "first" and "last" in these method names to avoid confusion.
+ charIndex = length;
+ int embeddingLevel = 0;
+ int lastNonEmptyEmbeddingLevel = 0;
+ while (charIndex > 0) {
+ switch (dirTypeBackward()) {
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
+ if (embeddingLevel == 0) {
+ return DIR_LTR;
+ }
+ if (lastNonEmptyEmbeddingLevel == 0) {
+ lastNonEmptyEmbeddingLevel = embeddingLevel;
+ }
+ break;
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
+ if (lastNonEmptyEmbeddingLevel == embeddingLevel) {
+ return DIR_LTR;
+ }
+ --embeddingLevel;
+ break;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+ if (embeddingLevel == 0) {
+ return DIR_RTL;
+ }
+ if (lastNonEmptyEmbeddingLevel == 0) {
+ lastNonEmptyEmbeddingLevel = embeddingLevel;
+ }
+ break;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
+ if (lastNonEmptyEmbeddingLevel == embeddingLevel) {
+ return DIR_RTL;
+ }
+ --embeddingLevel;
+ break;
+ case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
+ ++embeddingLevel;
+ break;
+ case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
+ break;
+ default:
+ if (lastNonEmptyEmbeddingLevel == 0) {
+ lastNonEmptyEmbeddingLevel = embeddingLevel;
+ }
+ break;
+ }
+ }
+ return DIR_UNKNOWN;
+ }
+
+ // Internal methods
+
+ /**
+ * Gets the bidi character class, i.e. Character.getDirectionality(), of a given char, using
+ * a cache for speed. Not designed for supplementary codepoints, whose results we do not
+ * cache.
+ */
+ private static byte getCachedDirectionality(char c) {
+ return c < DIR_TYPE_CACHE_SIZE ? DIR_TYPE_CACHE[c] : Character.getDirectionality(c);
+ }
+
+ /**
+ * Returns the Character.DIRECTIONALITY_... value of the next codepoint and advances
+ * charIndex. If isHtml, and the codepoint is '<' or '&', advances through the tag/entity,
+ * and returns Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to
+ * figure out the actual character, and return its dirtype, but treating it as whitespace is
+ * good enough for our purposes.
+ *
+ * @throws java.lang.IndexOutOfBoundsException if called when charIndex >= length or < 0.
+ */
+ byte dirTypeForward() {
+ lastChar = text.charAt(charIndex);
+ if (Character.isHighSurrogate(lastChar)) {
+ int codePoint = Character.codePointAt(text, charIndex);
+ charIndex += Character.charCount(codePoint);
+ return Character.getDirectionality(codePoint);
+ }
+ charIndex++;
+ byte dirType = getCachedDirectionality(lastChar);
+ if (isHtml) {
+ // Process tags and entities.
+ if (lastChar == '<') {
+ dirType = skipTagForward();
+ } else if (lastChar == '&') {
+ dirType = skipEntityForward();
+ }
+ }
+ return dirType;
+ }
+
+ /**
+ * Returns the Character.DIRECTIONALITY_... value of the preceding codepoint and advances
+ * charIndex backwards. If isHtml, and the codepoint is the end of a complete HTML tag or
+ * entity, advances over the whole tag/entity and returns
+ * Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to figure out the
+ * actual character, and return its dirtype, but treating it as whitespace is good enough
+ * for our purposes.
+ *
+ * @throws java.lang.IndexOutOfBoundsException if called when charIndex > length or <= 0.
+ */
+ byte dirTypeBackward() {
+ lastChar = text.charAt(charIndex - 1);
+ if (Character.isLowSurrogate(lastChar)) {
+ int codePoint = Character.codePointBefore(text, charIndex);
+ charIndex -= Character.charCount(codePoint);
+ return Character.getDirectionality(codePoint);
+ }
+ charIndex--;
+ byte dirType = getCachedDirectionality(lastChar);
+ if (isHtml) {
+ // Process tags and entities.
+ if (lastChar == '>') {
+ dirType = skipTagBackward();
+ } else if (lastChar == ';') {
+ dirType = skipEntityBackward();
+ }
+ }
+ return dirType;
+ }
+
+ /**
+ * Advances charIndex forward through an HTML tag (after the opening &lt; has already been
+ * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching &gt;,
+ * does not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the
+ * &lt; that hadn't been part of a tag after all).
+ */
+ private byte skipTagForward() {
+ int initialCharIndex = charIndex;
+ while (charIndex < length) {
+ lastChar = text.charAt(charIndex++);
+ if (lastChar == '>') {
+ // The end of the tag.
+ return Character.DIRECTIONALITY_WHITESPACE;
+ }
+ if (lastChar == '"' || lastChar == '\'') {
+ // Skip over a quoted attribute value inside the tag.
+ char quote = lastChar;
+ while (charIndex < length && (lastChar = text.charAt(charIndex++)) != quote) {}
+ }
+ }
+ // The original '<' wasn't the start of a tag after all.
+ charIndex = initialCharIndex;
+ lastChar = '<';
+ return Character.DIRECTIONALITY_OTHER_NEUTRALS;
+ }
+
+ /**
+ * Advances charIndex backward through an HTML tag (after the closing &gt; has already been
+ * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching &lt;, does
+ * not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the &gt;
+ * that hadn't been part of a tag after all). Nevertheless, the running time for calling
+ * skipTagBackward() in a loop remains linear in the size of the text, even for a text like
+ * "&gt;&gt;&gt;&gt;", because skipTagBackward() also stops looking for a matching &lt;
+ * when it encounters another &gt;.
+ */
+ private byte skipTagBackward() {
+ int initialCharIndex = charIndex;
+ while (charIndex > 0) {
+ lastChar = text.charAt(--charIndex);
+ if (lastChar == '<') {
+ // The start of the tag.
+ return Character.DIRECTIONALITY_WHITESPACE;
+ }
+ if (lastChar == '>') {
+ break;
+ }
+ if (lastChar == '"' || lastChar == '\'') {
+ // Skip over a quoted attribute value inside the tag.
+ char quote = lastChar;
+ while (charIndex > 0 && (lastChar = text.charAt(--charIndex)) != quote) {}
+ }
+ }
+ // The original '>' wasn't the end of a tag after all.
+ charIndex = initialCharIndex;
+ lastChar = '>';
+ return Character.DIRECTIONALITY_OTHER_NEUTRALS;
+ }
+
+ /**
+ * Advances charIndex forward through an HTML character entity tag (after the opening
+ * &amp; has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be
+ * best to figure out the actual character and return its dirtype, but this is good enough.
+ */
+ private byte skipEntityForward() {
+ while (charIndex < length && (lastChar = text.charAt(charIndex++)) != ';') {}
+ return Character.DIRECTIONALITY_WHITESPACE;
+ }
+
+ /**
+ * Advances charIndex backward through an HTML character entity tag (after the closing ;
+ * has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be best
+ * to figure out the actual character and return its dirtype, but this is good enough.
+ * If there is no matching &amp;, does not change charIndex and returns
+ * Character.DIRECTIONALITY_OTHER_NEUTRALS (for the ';' that did not start an entity after
+ * all). Nevertheless, the running time for calling skipEntityBackward() in a loop remains
+ * linear in the size of the text, even for a text like ";;;;;;;", because skipTagBackward()
+ * also stops looking for a matching &amp; when it encounters another ;.
+ */
+ private byte skipEntityBackward() {
+ int initialCharIndex = charIndex;
+ while (charIndex > 0) {
+ lastChar = text.charAt(--charIndex);
+ if (lastChar == '&') {
+ return Character.DIRECTIONALITY_WHITESPACE;
+ }
+ if (lastChar == ';') {
+ break;
+ }
+ }
+ charIndex = initialCharIndex;
+ lastChar = ';';
+ return Character.DIRECTIONALITY_OTHER_NEUTRALS;
+ }
+ }
+} \ No newline at end of file
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index d909362..122f8a1 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -503,8 +503,15 @@ public class DynamicLayout extends Layout
mNumberOfBlocks = newNumberOfBlocks;
final int deltaLines = newLineCount - (endLine - startLine + 1);
- for (int i = firstBlock + numAddedBlocks; i < mNumberOfBlocks; i++) {
- mBlockEndLines[i] += deltaLines;
+ if (deltaLines != 0) {
+ // Display list whose index is >= mIndexFirstChangedBlock is valid
+ // but it needs to update its drawing location.
+ mIndexFirstChangedBlock = firstBlock + numAddedBlocks;
+ for (int i = mIndexFirstChangedBlock; i < mNumberOfBlocks; i++) {
+ mBlockEndLines[i] += deltaLines;
+ }
+ } else {
+ mIndexFirstChangedBlock = mNumberOfBlocks;
}
int blockIndex = firstBlock;
@@ -559,6 +566,20 @@ public class DynamicLayout extends Layout
return mNumberOfBlocks;
}
+ /**
+ * @hide
+ */
+ public int getIndexFirstChangedBlock() {
+ return mIndexFirstChangedBlock;
+ }
+
+ /**
+ * @hide
+ */
+ public void setIndexFirstChangedBlock(int i) {
+ mIndexFirstChangedBlock = i;
+ }
+
@Override
public int getLineCount() {
return mInts.size() - 1;
@@ -697,6 +718,8 @@ public class DynamicLayout extends Layout
private int[] mBlockIndices;
// Number of items actually currently being used in the above 2 arrays
private int mNumberOfBlocks;
+ // The first index of the blocks whose locations are changed
+ private int mIndexFirstChangedBlock;
private int mTopPadding, mBottomPadding;
diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index 831ccc5..d426d12 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -58,13 +58,6 @@ extends CharSequence
int flags, float[] advances, int advancesIndex, Paint paint);
/**
- * Just like {@link Paint#getTextRunAdvances}.
- * @hide
- */
- float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
- int flags, float[] advances, int advancesIndex, Paint paint, int reserved);
-
- /**
* Just like {@link Paint#getTextRunCursor}.
* @hide
*/
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 1aab911..160c630 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -16,6 +16,7 @@
package android.text;
+import android.graphics.Color;
import com.android.internal.util.ArrayUtils;
import org.ccil.cowan.tagsoup.HTMLSchema;
import org.ccil.cowan.tagsoup.Parser;
@@ -168,7 +169,7 @@ public class Html {
for(int j = 0; j < style.length; j++) {
if (style[j] instanceof AlignmentSpan) {
- Layout.Alignment align =
+ Layout.Alignment align =
((AlignmentSpan) style[j]).getAlignment();
needDiv = true;
if (align == Layout.Alignment.ALIGN_CENTER) {
@@ -181,7 +182,7 @@ public class Html {
}
}
if (needDiv) {
- out.append("<div " + elements + ">");
+ out.append("<div ").append(elements).append(">");
}
withinDiv(out, text, i, next);
@@ -199,13 +200,13 @@ public class Html {
next = text.nextSpanTransition(i, end, QuoteSpan.class);
QuoteSpan[] quotes = text.getSpans(i, next, QuoteSpan.class);
- for (QuoteSpan quote: quotes) {
+ for (QuoteSpan quote : quotes) {
out.append("<blockquote>");
}
withinBlockquote(out, text, i, next);
- for (QuoteSpan quote: quotes) {
+ for (QuoteSpan quote : quotes) {
out.append("</blockquote>\n");
}
}
@@ -391,7 +392,7 @@ public class Html {
} else if (c == '&') {
out.append("&amp;");
} else if (c > 0x7E || c < ' ') {
- out.append("&#" + ((int) c) + ";");
+ out.append("&#").append((int) c).append(";");
} else if (c == ' ') {
while (i + 1 < end && text.charAt(i + 1) == ' ') {
out.append("&nbsp;");
@@ -616,8 +617,6 @@ class HtmlToSpannedConverter implements ContentHandler {
if (where != len) {
text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
-
- return;
}
private static void startImg(SpannableStringBuilder text,
@@ -673,7 +672,7 @@ class HtmlToSpannedConverter implements ContentHandler {
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
- int c = getHtmlColor(f.mColor);
+ int c = Color.getHtmlColor(f.mColor);
if (c != -1) {
text.setSpan(new ForegroundColorSpan(c | 0xFF000000),
where, len,
@@ -842,47 +841,4 @@ class HtmlToSpannedConverter implements ContentHandler {
mLevel = level;
}
}
-
- private static HashMap<String,Integer> COLORS = buildColorMap();
-
- private static HashMap<String,Integer> buildColorMap() {
- HashMap<String,Integer> map = new HashMap<String,Integer>();
- map.put("aqua", 0x00FFFF);
- map.put("black", 0x000000);
- map.put("blue", 0x0000FF);
- map.put("fuchsia", 0xFF00FF);
- map.put("green", 0x008000);
- map.put("grey", 0x808080);
- map.put("lime", 0x00FF00);
- map.put("maroon", 0x800000);
- map.put("navy", 0x000080);
- map.put("olive", 0x808000);
- map.put("purple", 0x800080);
- map.put("red", 0xFF0000);
- map.put("silver", 0xC0C0C0);
- map.put("teal", 0x008080);
- map.put("white", 0xFFFFFF);
- map.put("yellow", 0xFFFF00);
- return map;
- }
-
- /**
- * Converts an HTML color (named or numeric) to an integer RGB value.
- *
- * @param color Non-null color string.
- * @return A color value, or {@code -1} if the color string could not be interpreted.
- */
- private static int getHtmlColor(String color) {
- Integer i = COLORS.get(color.toLowerCase());
- if (i != null) {
- return i;
- } else {
- try {
- return XmlUtils.convertValueToInt(color, -1);
- } catch (NumberFormatException nfe) {
- return -1;
- }
- }
- }
-
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 123acca..a6e8c70 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -792,8 +792,17 @@ public abstract class Layout {
* the paragraph's primary direction.
*/
public float getPrimaryHorizontal(int offset) {
+ return getPrimaryHorizontal(offset, false /* not clamped */);
+ }
+
+ /**
+ * Get the primary horizontal position for the specified text offset, but
+ * optionally clamp it so that it doesn't exceed the width of the layout.
+ * @hide
+ */
+ public float getPrimaryHorizontal(int offset, boolean clamped) {
boolean trailing = primaryIsTrailingPrevious(offset);
- return getHorizontal(offset, trailing);
+ return getHorizontal(offset, trailing, clamped);
}
/**
@@ -802,17 +811,26 @@ public abstract class Layout {
* the direction other than the paragraph's primary direction.
*/
public float getSecondaryHorizontal(int offset) {
+ return getSecondaryHorizontal(offset, false /* not clamped */);
+ }
+
+ /**
+ * Get the secondary horizontal position for the specified text offset, but
+ * optionally clamp it so that it doesn't exceed the width of the layout.
+ * @hide
+ */
+ public float getSecondaryHorizontal(int offset, boolean clamped) {
boolean trailing = primaryIsTrailingPrevious(offset);
- return getHorizontal(offset, !trailing);
+ return getHorizontal(offset, !trailing, clamped);
}
- private float getHorizontal(int offset, boolean trailing) {
+ private float getHorizontal(int offset, boolean trailing, boolean clamped) {
int line = getLineForOffset(offset);
- return getHorizontal(offset, trailing, line);
+ return getHorizontal(offset, trailing, line, clamped);
}
- private float getHorizontal(int offset, boolean trailing, int line) {
+ private float getHorizontal(int offset, boolean trailing, int line, boolean clamped) {
int start = getLineStart(line);
int end = getLineEnd(line);
int dir = getParagraphDirection(line);
@@ -834,6 +852,9 @@ public abstract class Layout {
float wid = tl.measure(offset - start, trailing, null);
TextLine.recycle(tl);
+ if (clamped && wid > mWidth) {
+ wid = mWidth;
+ }
int left = getParagraphLeft(line);
int right = getParagraphRight(line);
@@ -1257,6 +1278,23 @@ public abstract class Layout {
}
/**
+ * Determine whether we should clamp cursor position. Currently it's
+ * only robust for left-aligned displays.
+ * @hide
+ */
+ public boolean shouldClampCursor(int line) {
+ // Only clamp cursor position in left-aligned displays.
+ switch (getParagraphAlignment(line)) {
+ case ALIGN_LEFT:
+ return true;
+ case ALIGN_NORMAL:
+ return getParagraphDirection(line) > 0;
+ default:
+ return false;
+ }
+
+ }
+ /**
* Fills in the specified Path with a representation of a cursor
* at the specified offset. This will often be a vertical line
* but can be multiple discontinuous lines in text with multiple
@@ -1270,8 +1308,9 @@ public abstract class Layout {
int top = getLineTop(line);
int bottom = getLineTop(line+1);
- float h1 = getPrimaryHorizontal(point) - 0.5f;
- float h2 = isLevelBoundary(point) ? getSecondaryHorizontal(point) - 0.5f : h1;
+ boolean clamped = shouldClampCursor(line);
+ float h1 = getPrimaryHorizontal(point, clamped) - 0.5f;
+ float h2 = isLevelBoundary(point) ? getSecondaryHorizontal(point, clamped) - 0.5f : h1;
int caps = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SHIFT_ON) |
TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SELECTING);
@@ -1357,8 +1396,8 @@ public abstract class Layout {
int en = Math.min(end, there);
if (st != en) {
- float h1 = getHorizontal(st, false, line);
- float h2 = getHorizontal(en, true, line);
+ float h1 = getHorizontal(st, false, line, false /* not clamped */);
+ float h2 = getHorizontal(en, true, line, false /* not clamped */);
float left = Math.min(h1, h2);
float right = Math.max(h1, h2);
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 0f30d25..8929930 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -1226,35 +1226,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
/**
- * Don't call this yourself -- exists for Paint to use internally.
- * {@hide}
- */
- public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
- float[] advances, int advancesPos, Paint p, int reserved) {
-
- float ret;
-
- int contextLen = contextEnd - contextStart;
- int len = end - start;
-
- if (end <= mGapStart) {
- ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
- flags, advances, advancesPos, reserved);
- } else if (start >= mGapStart) {
- ret = p.getTextRunAdvances(mText, start + mGapLength, len,
- contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved);
- } else {
- char[] buf = TextUtils.obtain(contextLen);
- getChars(contextStart, contextEnd, buf, 0);
- ret = p.getTextRunAdvances(buf, start - contextStart, len,
- 0, contextLen, flags, advances, advancesPos, reserved);
- TextUtils.recycle(buf);
- }
-
- return ret;
- }
-
- /**
* Returns the next cursor position in the run. This avoids placing the cursor between
* surrogates, between characters that form conjuncts, between base characters and combining
* marks, or within a reordering cluster.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 9051285..1291279 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -340,7 +340,7 @@ public class StaticLayout extends Layout {
w += widths[j - paraStart];
}
- boolean isSpaceOrTab = c == CHAR_SPACE || c == CHAR_TAB;
+ boolean isSpaceOrTab = c == CHAR_SPACE || c == CHAR_TAB || c == CHAR_ZWSP;
if (w <= width || isSpaceOrTab) {
fitWidth = w;
@@ -956,6 +956,7 @@ public class StaticLayout extends Layout {
private static final char CHAR_SPACE = ' ';
private static final char CHAR_SLASH = '/';
private static final char CHAR_HYPHEN = '-';
+ private static final char CHAR_ZWSP = '\u200B';
private static final double EXTRA_ROUNDING = 0.5;
diff --git a/core/java/android/text/TextDirectionHeuristic.java b/core/java/android/text/TextDirectionHeuristic.java
index 513e11c..8a4ba42 100644
--- a/core/java/android/text/TextDirectionHeuristic.java
+++ b/core/java/android/text/TextDirectionHeuristic.java
@@ -17,10 +17,30 @@
package android.text;
/**
- * Interface for objects that guess at the paragraph direction by examining text.
- *
- * @hide
+ * Interface for objects that use a heuristic for guessing at the paragraph direction by examining text.
*/
public interface TextDirectionHeuristic {
- boolean isRtl(char[] text, int start, int count);
+ /**
+ * Guess if a chars array is in the RTL direction or not.
+ *
+ * @param array the char array.
+ * @param start start index, inclusive.
+ * @param count the length to check, must not be negative and not greater than
+ * {@code array.length - start}.
+ * @return true if all chars in the range are to be considered in a RTL direction,
+ * false otherwise.
+ */
+ boolean isRtl(char[] array, int start, int count);
+
+ /**
+ * Guess if a {@code CharSequence} is in the RTL direction or not.
+ *
+ * @param cs the CharSequence.
+ * @param start start index, inclusive.
+ * @param count the length to check, must not be negative and not greater than
+ * {@code CharSequence.length() - start}.
+ * @return true if all chars in the range are to be considered in a RTL direction,
+ * false otherwise.
+ */
+ boolean isRtl(CharSequence cs, int start, int count);
}
diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java
index df8c4c6..866137c 100644
--- a/core/java/android/text/TextDirectionHeuristics.java
+++ b/core/java/android/text/TextDirectionHeuristics.java
@@ -19,43 +19,52 @@ package android.text;
import android.view.View;
+import java.nio.CharBuffer;
+
/**
- * Some objects that implement TextDirectionHeuristic.
+ * Some objects that implement {@link TextDirectionHeuristic}. Use these with
+ * the {@link BidiFormatter#unicodeWrap unicodeWrap()} methods in {@link BidiFormatter}.
+ * Also notice that these direction heuristics correspond to the same types of constants
+ * provided in the {@link android.view.View} class for {@link android.view.View#setTextDirection
+ * setTextDirection()}, such as {@link android.view.View#TEXT_DIRECTION_RTL}.
+ * <p>To support versions lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * you can use the support library's {@link android.support.v4.text.TextDirectionHeuristicsCompat}
+ * class.
*
- * @hide
*/
public class TextDirectionHeuristics {
- /** Always decides that the direction is left to right. */
+ /**
+ * Always decides that the direction is left to right.
+ */
public static final TextDirectionHeuristic LTR =
new TextDirectionHeuristicInternal(null /* no algorithm */, false);
- /** Always decides that the direction is right to left. */
+ /**
+ * Always decides that the direction is right to left.
+ */
public static final TextDirectionHeuristic RTL =
new TextDirectionHeuristicInternal(null /* no algorithm */, true);
/**
- * Determines the direction based on the first strong directional character,
- * including bidi format chars, falling back to left to right if it
- * finds none. This is the default behavior of the Unicode Bidirectional
- * Algorithm.
+ * Determines the direction based on the first strong directional character, including bidi
+ * format chars, falling back to left to right if it finds none. This is the default behavior
+ * of the Unicode Bidirectional Algorithm.
*/
public static final TextDirectionHeuristic FIRSTSTRONG_LTR =
new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, false);
/**
- * Determines the direction based on the first strong directional character,
- * including bidi format chars, falling back to right to left if it
- * finds none. This is similar to the default behavior of the Unicode
- * Bidirectional Algorithm, just with different fallback behavior.
+ * Determines the direction based on the first strong directional character, including bidi
+ * format chars, falling back to right to left if it finds none. This is similar to the default
+ * behavior of the Unicode Bidirectional Algorithm, just with different fallback behavior.
*/
public static final TextDirectionHeuristic FIRSTSTRONG_RTL =
new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, true);
/**
- * If the text contains any strong right to left non-format character, determines
- * that the direction is right to left, falling back to left to right if it
- * finds none.
+ * If the text contains any strong right to left non-format character, determines that the
+ * direction is right to left, falling back to left to right if it finds none.
*/
public static final TextDirectionHeuristic ANYRTL_LTR =
new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_RTL, false);
@@ -65,8 +74,39 @@ public class TextDirectionHeuristics {
*/
public static final TextDirectionHeuristic LOCALE = TextDirectionHeuristicLocale.INSTANCE;
- private static enum TriState {
- TRUE, FALSE, UNKNOWN;
+ /**
+ * State constants for taking care about true / false / unknown
+ */
+ private static final int STATE_TRUE = 0;
+ private static final int STATE_FALSE = 1;
+ private static final int STATE_UNKNOWN = 2;
+
+ private static int isRtlText(int directionality) {
+ switch (directionality) {
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
+ return STATE_FALSE;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+ return STATE_TRUE;
+ default:
+ return STATE_UNKNOWN;
+ }
+ }
+
+ private static int isRtlTextOrFormat(int directionality) {
+ switch (directionality) {
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
+ return STATE_FALSE;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
+ return STATE_TRUE;
+ default:
+ return STATE_UNKNOWN;
+ }
}
/**
@@ -87,21 +127,26 @@ public class TextDirectionHeuristics {
abstract protected boolean defaultIsRtl();
@Override
- public boolean isRtl(char[] chars, int start, int count) {
- if (chars == null || start < 0 || count < 0 || chars.length - count < start) {
+ public boolean isRtl(char[] array, int start, int count) {
+ return isRtl(CharBuffer.wrap(array), start, count);
+ }
+
+ @Override
+ public boolean isRtl(CharSequence cs, int start, int count) {
+ if (cs == null || start < 0 || count < 0 || cs.length() - count < start) {
throw new IllegalArgumentException();
}
if (mAlgorithm == null) {
return defaultIsRtl();
}
- return doCheck(chars, start, count);
+ return doCheck(cs, start, count);
}
- private boolean doCheck(char[] chars, int start, int count) {
- switch(mAlgorithm.checkRtl(chars, start, count)) {
- case TRUE:
+ private boolean doCheck(CharSequence cs, int start, int count) {
+ switch(mAlgorithm.checkRtl(cs, start, count)) {
+ case STATE_TRUE:
return true;
- case FALSE:
+ case STATE_FALSE:
return false;
default:
return defaultIsRtl();
@@ -124,58 +169,26 @@ public class TextDirectionHeuristics {
}
}
- private static TriState isRtlText(int directionality) {
- switch (directionality) {
- case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
- return TriState.FALSE;
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
- return TriState.TRUE;
- default:
- return TriState.UNKNOWN;
- }
- }
-
- private static TriState isRtlTextOrFormat(int directionality) {
- switch (directionality) {
- case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
- case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
- case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
- return TriState.FALSE;
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
- return TriState.TRUE;
- default:
- return TriState.UNKNOWN;
- }
- }
-
/**
* Interface for an algorithm to guess the direction of a paragraph of text.
- *
*/
private static interface TextDirectionAlgorithm {
/**
* Returns whether the range of text is RTL according to the algorithm.
- *
*/
- TriState checkRtl(char[] text, int start, int count);
+ int checkRtl(CharSequence cs, int start, int count);
}
/**
- * Algorithm that uses the first strong directional character to determine
- * the paragraph direction. This is the standard Unicode Bidirectional
- * algorithm.
- *
+ * Algorithm that uses the first strong directional character to determine the paragraph
+ * direction. This is the standard Unicode Bidirectional algorithm.
*/
private static class FirstStrong implements TextDirectionAlgorithm {
@Override
- public TriState checkRtl(char[] text, int start, int count) {
- TriState result = TriState.UNKNOWN;
- for (int i = start, e = start + count; i < e && result == TriState.UNKNOWN; ++i) {
- result = isRtlTextOrFormat(Character.getDirectionality(text[i]));
+ public int checkRtl(CharSequence cs, int start, int count) {
+ int result = STATE_UNKNOWN;
+ for (int i = start, e = start + count; i < e && result == STATE_UNKNOWN; ++i) {
+ result = isRtlTextOrFormat(Character.getDirectionality(cs.charAt(i)));
}
return result;
}
@@ -190,25 +203,24 @@ public class TextDirectionHeuristics {
* Algorithm that uses the presence of any strong directional non-format
* character (e.g. excludes LRE, LRO, RLE, RLO) to determine the
* direction of text.
- *
*/
private static class AnyStrong implements TextDirectionAlgorithm {
private final boolean mLookForRtl;
@Override
- public TriState checkRtl(char[] text, int start, int count) {
+ public int checkRtl(CharSequence cs, int start, int count) {
boolean haveUnlookedFor = false;
for (int i = start, e = start + count; i < e; ++i) {
- switch (isRtlText(Character.getDirectionality(text[i]))) {
- case TRUE:
+ switch (isRtlText(Character.getDirectionality(cs.charAt(i)))) {
+ case STATE_TRUE:
if (mLookForRtl) {
- return TriState.TRUE;
+ return STATE_TRUE;
}
haveUnlookedFor = true;
break;
- case FALSE:
+ case STATE_FALSE:
if (!mLookForRtl) {
- return TriState.FALSE;
+ return STATE_FALSE;
}
haveUnlookedFor = true;
break;
@@ -217,9 +229,9 @@ public class TextDirectionHeuristics {
}
}
if (haveUnlookedFor) {
- return mLookForRtl ? TriState.FALSE : TriState.TRUE;
+ return mLookForRtl ? STATE_FALSE : STATE_TRUE;
}
- return TriState.UNKNOWN;
+ return STATE_UNKNOWN;
}
private AnyStrong(boolean lookForRtl) {
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 1508d10..e2035c2 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -44,6 +44,7 @@ import android.text.style.TextAppearanceSpan;
import android.text.style.TypefaceSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
+import android.util.Log;
import android.util.Printer;
import android.view.View;
@@ -57,6 +58,8 @@ import java.util.Locale;
import java.util.regex.Pattern;
public class TextUtils {
+ private static final String TAG = "TextUtils";
+
private TextUtils() { /* cannot be instantiated */ }
@@ -550,6 +553,8 @@ public class TextUtils {
/** @hide */
public static final int ALIGNMENT_SPAN = 1;
/** @hide */
+ public static final int FIRST_SPAN = ALIGNMENT_SPAN;
+ /** @hide */
public static final int FOREGROUND_COLOR_SPAN = 2;
/** @hide */
public static final int RELATIVE_SIZE_SPAN = 3;
@@ -593,6 +598,8 @@ public class TextUtils {
public static final int EASY_EDIT_SPAN = 22;
/** @hide */
public static final int LOCALE_SPAN = 23;
+ /** @hide */
+ public static final int LAST_SPAN = LOCALE_SPAN;
/**
* Flatten a CharSequence and whatever styles can be copied across processes
@@ -622,9 +629,16 @@ public class TextUtils {
if (prop instanceof ParcelableSpan) {
ParcelableSpan ps = (ParcelableSpan)prop;
- p.writeInt(ps.getSpanTypeId());
- ps.writeToParcel(p, parcelableFlags);
- writeWhere(p, sp, o);
+ int spanTypeId = ps.getSpanTypeId();
+ if (spanTypeId < FIRST_SPAN || spanTypeId > LAST_SPAN) {
+ Log.e(TAG, "external class \"" + ps.getClass().getSimpleName()
+ + "\" is attempting to use the frameworks-only ParcelableSpan"
+ + " interface");
+ } else {
+ p.writeInt(spanTypeId);
+ ps.writeToParcel(p, parcelableFlags);
+ writeWhere(p, sp, o);
+ }
}
}
@@ -757,7 +771,7 @@ public class TextUtils {
break;
case EASY_EDIT_SPAN:
- readSpan(p, sp, new EasyEditSpan());
+ readSpan(p, sp, new EasyEditSpan(p));
break;
case LOCALE_SPAN:
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index 50b1a29..c497e35 100644
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -31,6 +31,7 @@ import java.util.Locale;
import java.util.TimeZone;
import java.text.SimpleDateFormat;
+import libcore.icu.ICU;
import libcore.icu.LocaleData;
/**
@@ -43,6 +44,9 @@ import libcore.icu.LocaleData;
* for both formatting and parsing dates. For the canonical documentation
* of format strings, see {@link java.text.SimpleDateFormat}.
*
+ * <p>In cases where the system does not provide a suitable pattern,
+ * this class offers the {@link #getBestDateTimePattern} method.
+ *
* <p>The {@code format} methods in this class implement a subset of Unicode
* <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a> patterns.
* The subset currently supported by this class includes the following format characters:
@@ -164,6 +168,37 @@ public class DateFormat {
}
/**
+ * Returns the best possible localized form of the given skeleton for the given
+ * locale. A skeleton is similar to, and uses the same format characters as, a Unicode
+ * <a href="http://www.unicode.org/reports/tr35/#Date_Format_Patterns">UTS #35</a>
+ * pattern.
+ *
+ * <p>One difference is that order is irrelevant. For example, "MMMMd" will return
+ * "MMMM d" in the {@code en_US} locale, but "d. MMMM" in the {@code de_CH} locale.
+ *
+ * <p>Note also in that second example that the necessary punctuation for German was
+ * added. For the same input in {@code es_ES}, we'd have even more extra text:
+ * "d 'de' MMMM".
+ *
+ * <p>This method will automatically correct for grammatical necessity. Given the
+ * same "MMMMd" input, this method will return "d LLLL" in the {@code fa_IR} locale,
+ * where stand-alone months are necessary. Lengths are preserved where meaningful,
+ * so "Md" would give a different result to "MMMd", say, except in a locale such as
+ * {@code ja_JP} where there is only one length of month.
+ *
+ * <p>This method will only return patterns that are in CLDR, and is useful whenever
+ * you know what elements you want in your format string but don't want to make your
+ * code specific to any one locale.
+ *
+ * @param locale the locale into which the skeleton should be localized
+ * @param skeleton a skeleton as described above
+ * @return a string pattern suitable for use with {@link java.text.SimpleDateFormat}.
+ */
+ public static String getBestDateTimePattern(Locale locale, String skeleton) {
+ return ICU.getBestDateTimePattern(skeleton, locale.toString());
+ }
+
+ /**
* Returns a {@link java.text.DateFormat} object that can format the time according
* to the current locale and the user's 12-/24-hour clock preference.
* @param context the application context
@@ -356,6 +391,16 @@ public class DateFormat {
* @hide
*/
public static boolean hasSeconds(CharSequence inFormat) {
+ return hasDesignator(inFormat, SECONDS);
+ }
+
+ /**
+ * Test if a format string contains the given designator. Always returns
+ * {@code false} if the input format is {@code null}.
+ *
+ * @hide
+ */
+ public static boolean hasDesignator(CharSequence inFormat, char designator) {
if (inFormat == null) return false;
final int length = inFormat.length();
@@ -369,7 +414,7 @@ public class DateFormat {
if (c == QUOTE) {
count = skipQuotedText(inFormat, i, length);
- } else if (c == SECONDS) {
+ } else if (c == designator) {
return true;
}
}
diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java
index 3d9daed..c95df46 100644
--- a/core/java/android/text/method/DigitsKeyListener.java
+++ b/core/java/android/text/method/DigitsKeyListener.java
@@ -49,13 +49,22 @@ public class DigitsKeyListener extends NumberKeyListener
* @see KeyEvent#getMatch
* @see #getAcceptedChars
*/
- private static final char[][] CHARACTERS = new char[][] {
- new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
- new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' },
- new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' },
- new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.' },
+ private static final char[][] CHARACTERS = {
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+' },
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' },
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', '.' },
};
+ private static boolean isSignChar(final char c) {
+ return c == '-' || c == '+';
+ }
+
+ // TODO: Needs internationalization
+ private static boolean isDecimalPointChar(final char c) {
+ return c == '.';
+ }
+
/**
* Allocates a DigitsKeyListener that accepts the digits 0 through 9.
*/
@@ -145,32 +154,32 @@ public class DigitsKeyListener extends NumberKeyListener
int dlen = dest.length();
/*
- * Find out if the existing text has '-' or '.' characters.
+ * Find out if the existing text has a sign or decimal point characters.
*/
for (int i = 0; i < dstart; i++) {
char c = dest.charAt(i);
- if (c == '-') {
+ if (isSignChar(c)) {
sign = i;
- } else if (c == '.') {
+ } else if (isDecimalPointChar(c)) {
decimal = i;
}
}
for (int i = dend; i < dlen; i++) {
char c = dest.charAt(i);
- if (c == '-') {
- return ""; // Nothing can be inserted in front of a '-'.
- } else if (c == '.') {
+ if (isSignChar(c)) {
+ return ""; // Nothing can be inserted in front of a sign character.
+ } else if (isDecimalPointChar(c)) {
decimal = i;
}
}
/*
* If it does, we must strip them out from the source.
- * In addition, '-' must be the very first character,
- * and nothing can be inserted before an existing '-'.
+ * In addition, a sign character must be the very first character,
+ * and nothing can be inserted before an existing sign character.
* Go in reverse order so the offsets are stable.
*/
@@ -180,7 +189,7 @@ public class DigitsKeyListener extends NumberKeyListener
char c = source.charAt(i);
boolean strip = false;
- if (c == '-') {
+ if (isSignChar(c)) {
if (i != start || dstart != 0) {
strip = true;
} else if (sign >= 0) {
@@ -188,7 +197,7 @@ public class DigitsKeyListener extends NumberKeyListener
} else {
sign = i;
}
- } else if (c == '.') {
+ } else if (isDecimalPointChar(c)) {
if (decimal >= 0) {
strip = true;
} else {
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index c5261f3..98316ae 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -180,6 +180,7 @@ public class QwertyKeyListener extends BaseKeyListener {
if (composed != 0) {
i = composed;
replace = true;
+ dead = false;
}
}
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index 3dfd44d..9394a0b 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -64,7 +64,9 @@ public class Touch {
if (actualWidth < availableWidth) {
if (a == Alignment.ALIGN_CENTER) {
x = left - ((availableWidth - actualWidth) / 2);
- } else if ((ltr && (a == Alignment.ALIGN_OPPOSITE)) || (a == Alignment.ALIGN_RIGHT)) {
+ } else if ((ltr && (a == Alignment.ALIGN_OPPOSITE)) ||
+ (!ltr && (a == Alignment.ALIGN_NORMAL)) ||
+ (a == Alignment.ALIGN_RIGHT)) {
// align_opposite does NOT mean align_right, we need the paragraph
// direction to resolve it to left or right
x = left - (availableWidth - actualWidth);
diff --git a/core/java/android/text/style/EasyEditSpan.java b/core/java/android/text/style/EasyEditSpan.java
index 2feb719..03b4f60 100644
--- a/core/java/android/text/style/EasyEditSpan.java
+++ b/core/java/android/text/style/EasyEditSpan.java
@@ -16,6 +16,7 @@
package android.text.style;
+import android.app.PendingIntent;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextUtils;
@@ -25,12 +26,62 @@ import android.widget.TextView;
* Provides an easy way to edit a portion of text.
* <p>
* The {@link TextView} uses this span to allow the user to delete a chuck of text in one click.
- * the text. {@link TextView} removes this span as soon as the text is edited, or the cursor moves.
+ * <p>
+ * {@link TextView} removes the span when the user deletes the whole text or modifies it.
+ * <p>
+ * This span can be also used to receive notification when the user deletes or modifies the text;
*/
public class EasyEditSpan implements ParcelableSpan {
+ /**
+ * The extra key field in the pending intent that describes how the text changed.
+ *
+ * @see #TEXT_DELETED
+ * @see #TEXT_MODIFIED
+ * @see #getPendingIntent()
+ */
+ public static final String EXTRA_TEXT_CHANGED_TYPE =
+ "android.text.style.EXTRA_TEXT_CHANGED_TYPE";
+
+ /**
+ * The value of {@link #EXTRA_TEXT_CHANGED_TYPE} when the text wrapped by this span is deleted.
+ */
+ public static final int TEXT_DELETED = 1;
+
+ /**
+ * The value of {@link #EXTRA_TEXT_CHANGED_TYPE} when the text wrapped by this span is modified.
+ */
+ public static final int TEXT_MODIFIED = 2;
+
+ private final PendingIntent mPendingIntent;
+
+ private boolean mDeleteEnabled;
+
+ /**
+ * Creates the span. No intent is sent when the wrapped text is modified or
+ * deleted.
+ */
public EasyEditSpan() {
- // Empty
+ mPendingIntent = null;
+ mDeleteEnabled = true;
+ }
+
+ /**
+ * @param pendingIntent The intent will be sent when the wrapped text is deleted or modified.
+ * When the pending intent is sent, {@link #EXTRA_TEXT_CHANGED_TYPE} is
+ * added in the intent to describe how the text changed.
+ */
+ public EasyEditSpan(PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ mDeleteEnabled = true;
+ }
+
+ /**
+ * Constructor called from {@link TextUtils} to restore the span.
+ */
+ public EasyEditSpan(Parcel source) {
+ mPendingIntent = source.readParcelable(null);
+ mDeleteEnabled = (source.readByte() == 1);
}
@Override
@@ -40,11 +91,39 @@ public class EasyEditSpan implements ParcelableSpan {
@Override
public void writeToParcel(Parcel dest, int flags) {
- // Empty
+ dest.writeParcelable(mPendingIntent, 0);
+ dest.writeByte((byte) (mDeleteEnabled ? 1 : 0));
}
@Override
public int getSpanTypeId() {
return TextUtils.EASY_EDIT_SPAN;
}
+
+ /**
+ * @return True if the {@link TextView} should offer the ability to delete the text.
+ *
+ * @hide
+ */
+ public boolean isDeleteEnabled() {
+ return mDeleteEnabled;
+ }
+
+ /**
+ * Enables or disables the deletion of the text.
+ *
+ * @hide
+ */
+ public void setDeleteEnabled(boolean value) {
+ mDeleteEnabled = value;
+ }
+
+ /**
+ * @return the pending intent to send when the wrapped text is deleted or modified.
+ *
+ * @hide
+ */
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
}
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 5dc206f..0ec7e84 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -17,6 +17,7 @@
package android.text.style;
import android.content.Context;
+import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Parcel;
@@ -26,6 +27,7 @@ import android.text.ParcelableSpan;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import java.util.Arrays;
@@ -45,6 +47,8 @@ import java.util.Locale;
*/
public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
+ private static final String TAG = "SuggestionSpan";
+
/**
* Sets this flag if the suggestions should be easily accessible with few interactions.
* This flag should be set for every suggestions that the user is likely to use.
@@ -82,6 +86,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
private final String[] mSuggestions;
private final String mLocaleString;
private final String mNotificationTargetClassName;
+ private final String mNotificationTargetPackageName;
private final int mHashCode;
private float mEasyCorrectUnderlineThickness;
@@ -134,6 +139,12 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
mLocaleString = "";
}
+ if (context != null) {
+ mNotificationTargetPackageName = context.getPackageName();
+ } else {
+ mNotificationTargetPackageName = null;
+ }
+
if (notificationTargetClass != null) {
mNotificationTargetClassName = notificationTargetClass.getCanonicalName();
} else {
@@ -185,6 +196,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
mFlags = src.readInt();
mLocaleString = src.readString();
mNotificationTargetClassName = src.readString();
+ mNotificationTargetPackageName = src.readString();
mHashCode = src.readInt();
mEasyCorrectUnderlineColor = src.readInt();
mEasyCorrectUnderlineThickness = src.readFloat();
@@ -240,6 +252,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
dest.writeInt(mFlags);
dest.writeString(mLocaleString);
dest.writeString(mNotificationTargetClassName);
+ dest.writeString(mNotificationTargetPackageName);
dest.writeInt(mHashCode);
dest.writeInt(mEasyCorrectUnderlineColor);
dest.writeFloat(mEasyCorrectUnderlineThickness);
@@ -325,4 +338,40 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
}
return 0;
}
+
+ /**
+ * Notifies a suggestion selection.
+ *
+ * @hide
+ */
+ public void notifySelection(Context context, String original, int index) {
+ final Intent intent = new Intent();
+
+ if (context == null || mNotificationTargetClassName == null) {
+ return;
+ }
+ // Ensures that only a class in the original IME package will receive the
+ // notification.
+ if (mSuggestions == null || index < 0 || index >= mSuggestions.length) {
+ Log.w(TAG, "Unable to notify the suggestion as the index is out of range index=" + index
+ + " length=" + mSuggestions.length);
+ return;
+ }
+
+ // The package name is not mandatory (legacy from JB), and if the package name
+ // is missing, we try to notify the suggestion through the input method manager.
+ if (mNotificationTargetPackageName != null) {
+ intent.setClassName(mNotificationTargetPackageName, mNotificationTargetClassName);
+ intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
+ intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, original);
+ intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, mSuggestions[index]);
+ intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, hashCode());
+ context.sendBroadcast(intent);
+ } else {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.notifySuggestionPicked(this, original, index);
+ }
+ }
+ }
}
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index 9860588..2bc1c6a 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -16,6 +16,7 @@
package android.text.util;
+import android.telephony.PhoneNumberUtils;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.text.style.URLSpan;
@@ -32,9 +33,14 @@ import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import com.android.i18n.phonenumbers.PhoneNumberMatch;
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
+import com.android.i18n.phonenumbers.PhoneNumberUtil.Leniency;
+
/**
* Linkify take a piece of text and a regular expression and turns all of the
* regex matches in the text into clickable links. This is particularly
@@ -221,9 +227,7 @@ public class Linkify {
}
if ((mask & PHONE_NUMBERS) != 0) {
- gatherLinks(links, text, Patterns.PHONE,
- new String[] { "tel:" },
- sPhoneNumberMatchFilter, sPhoneNumberTransformFilter);
+ gatherTelLinks(links, text);
}
if ((mask & MAP_ADDRESSES) != 0) {
@@ -443,6 +447,19 @@ public class Linkify {
}
}
+ private static final void gatherTelLinks(ArrayList<LinkSpec> links, Spannable s) {
+ PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
+ Iterable<PhoneNumberMatch> matches = phoneUtil.findNumbers(s.toString(),
+ Locale.getDefault().getCountry(), Leniency.POSSIBLE, Long.MAX_VALUE);
+ for (PhoneNumberMatch match : matches) {
+ LinkSpec spec = new LinkSpec();
+ spec.url = "tel:" + PhoneNumberUtils.normalizeNumber(match.rawString());
+ spec.start = match.start();
+ spec.end = match.end();
+ links.add(spec);
+ }
+ }
+
private static final void gatherMapLinks(ArrayList<LinkSpec> links, Spannable s) {
String string = s.toString();
String address;
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index e856501..dae47b8 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -74,6 +74,15 @@ public class DisplayMetrics {
public static final int DENSITY_XXHIGH = 480;
/**
+ * Standard quantized DPI for extra-extra-extra-high-density screens. Applications
+ * should not generally worry about this density; relying on XHIGH graphics
+ * being scaled up to it should be sufficient for almost all cases. A typical
+ * use of this density would be 4K television screens -- 3840x2160, which
+ * is 2x a traditional HD 1920x1080 screen which runs at DENSITY_XHIGH.
+ */
+ public static final int DENSITY_XXXHIGH = 640;
+
+ /**
* The reference density used throughout the system.
*/
public static final int DENSITY_DEFAULT = DENSITY_MEDIUM;
diff --git a/core/java/android/util/FinitePool.java b/core/java/android/util/FinitePool.java
deleted file mode 100644
index b30f2bf..0000000
--- a/core/java/android/util/FinitePool.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-/**
- * @hide
- */
-class FinitePool<T extends Poolable<T>> implements Pool<T> {
- private static final String LOG_TAG = "FinitePool";
-
- /**
- * Factory used to create new pool objects
- */
- private final PoolableManager<T> mManager;
- /**
- * Maximum number of objects in the pool
- */
- private final int mLimit;
- /**
- * If true, mLimit is ignored
- */
- private final boolean mInfinite;
-
- /**
- * Next object to acquire
- */
- private T mRoot;
- /**
- * Number of objects in the pool
- */
- private int mPoolCount;
-
- FinitePool(PoolableManager<T> manager) {
- mManager = manager;
- mLimit = 0;
- mInfinite = true;
- }
-
- FinitePool(PoolableManager<T> manager, int limit) {
- if (limit <= 0) throw new IllegalArgumentException("The pool limit must be > 0");
-
- mManager = manager;
- mLimit = limit;
- mInfinite = false;
- }
-
- public T acquire() {
- T element;
-
- if (mRoot != null) {
- element = mRoot;
- mRoot = element.getNextPoolable();
- mPoolCount--;
- } else {
- element = mManager.newInstance();
- }
-
- if (element != null) {
- element.setNextPoolable(null);
- element.setPooled(false);
- mManager.onAcquired(element);
- }
-
- return element;
- }
-
- public void release(T element) {
- if (!element.isPooled()) {
- if (mInfinite || mPoolCount < mLimit) {
- mPoolCount++;
- element.setNextPoolable(mRoot);
- element.setPooled(true);
- mRoot = element;
- }
- mManager.onReleased(element);
- } else {
- Log.w(LOG_TAG, "Element is already in pool: " + element);
- }
- }
-}
diff --git a/core/java/android/util/LongSparseLongArray.java b/core/java/android/util/LongSparseLongArray.java
new file mode 100644
index 0000000..34b6126
--- /dev/null
+++ b/core/java/android/util/LongSparseLongArray.java
@@ -0,0 +1,228 @@
+/*
+ * 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.util;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+
+/**
+ * Map of {@code long} to {@code long}. Unlike a normal array of longs, there
+ * can be gaps in the indices. It is intended to be more efficient than using a
+ * {@code HashMap}.
+ *
+ * @hide
+ */
+public class LongSparseLongArray implements Cloneable {
+ private long[] mKeys;
+ private long[] mValues;
+ private int mSize;
+
+ /**
+ * Creates a new SparseLongArray containing no mappings.
+ */
+ public LongSparseLongArray() {
+ this(10);
+ }
+
+ /**
+ * Creates a new SparseLongArray containing no mappings that will not
+ * require any additional memory allocation to store the specified
+ * number of mappings.
+ */
+ public LongSparseLongArray(int initialCapacity) {
+ initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
+
+ mKeys = new long[initialCapacity];
+ mValues = new long[initialCapacity];
+ mSize = 0;
+ }
+
+ @Override
+ public LongSparseLongArray clone() {
+ LongSparseLongArray clone = null;
+ try {
+ clone = (LongSparseLongArray) super.clone();
+ clone.mKeys = mKeys.clone();
+ clone.mValues = mValues.clone();
+ } catch (CloneNotSupportedException cnse) {
+ /* ignore */
+ }
+ return clone;
+ }
+
+ /**
+ * Gets the long mapped from the specified key, or <code>0</code>
+ * if no such mapping has been made.
+ */
+ public long get(long key) {
+ return get(key, 0);
+ }
+
+ /**
+ * Gets the long mapped from the specified key, or the specified value
+ * if no such mapping has been made.
+ */
+ public long get(long key, long valueIfKeyNotFound) {
+ int i = Arrays.binarySearch(mKeys, 0, mSize, key);
+
+ if (i < 0) {
+ return valueIfKeyNotFound;
+ } else {
+ return mValues[i];
+ }
+ }
+
+ /**
+ * Removes the mapping from the specified key, if there was any.
+ */
+ public void delete(long key) {
+ int i = Arrays.binarySearch(mKeys, 0, mSize, key);
+
+ if (i >= 0) {
+ removeAt(i);
+ }
+ }
+
+ /**
+ * Removes the mapping at the given index.
+ */
+ public void removeAt(int index) {
+ System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
+ System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
+ mSize--;
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * replacing the previous mapping from the specified key if there
+ * was one.
+ */
+ public void put(long key, long value) {
+ int i = Arrays.binarySearch(mKeys, 0, mSize, key);
+
+ if (i >= 0) {
+ mValues[i] = value;
+ } else {
+ i = ~i;
+
+ if (mSize >= mKeys.length) {
+ growKeyAndValueArrays(mSize + 1);
+ }
+
+ if (mSize - i != 0) {
+ System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+ System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+ }
+
+ mKeys[i] = key;
+ mValues[i] = value;
+ mSize++;
+ }
+ }
+
+ /**
+ * Returns the number of key-value mappings that this SparseIntArray
+ * currently stores.
+ */
+ public int size() {
+ return mSize;
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the key from the <code>index</code>th key-value mapping that this
+ * SparseLongArray stores.
+ */
+ public long keyAt(int index) {
+ return mKeys[index];
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the value from the <code>index</code>th key-value mapping that this
+ * SparseLongArray stores.
+ */
+ public long valueAt(int index) {
+ return mValues[index];
+ }
+
+ /**
+ * Returns the index for which {@link #keyAt} would return the
+ * specified key, or a negative number if the specified
+ * key is not mapped.
+ */
+ public int indexOfKey(long key) {
+ return Arrays.binarySearch(mKeys, 0, mSize, key);
+ }
+
+ /**
+ * Returns an index for which {@link #valueAt} would return the
+ * specified key, or a negative number if no keys map to the
+ * specified value.
+ * Beware that this is a linear search, unlike lookups by key,
+ * and that multiple keys can map to the same value and this will
+ * find only one of them.
+ */
+ public int indexOfValue(long value) {
+ for (int i = 0; i < mSize; i++)
+ if (mValues[i] == value)
+ return i;
+
+ return -1;
+ }
+
+ /**
+ * Removes all key-value mappings from this SparseIntArray.
+ */
+ public void clear() {
+ mSize = 0;
+ }
+
+ /**
+ * Puts a key/value pair into the array, optimizing for the case where
+ * the key is greater than all existing keys in the array.
+ */
+ public void append(long key, long value) {
+ if (mSize != 0 && key <= mKeys[mSize - 1]) {
+ put(key, value);
+ return;
+ }
+
+ int pos = mSize;
+ if (pos >= mKeys.length) {
+ growKeyAndValueArrays(pos + 1);
+ }
+
+ mKeys[pos] = key;
+ mValues[pos] = value;
+ mSize = pos + 1;
+ }
+
+ private void growKeyAndValueArrays(int minNeededSize) {
+ int n = ArrayUtils.idealLongArraySize(minNeededSize);
+
+ long[] nkeys = new long[n];
+ long[] nvalues = new long[n];
+
+ System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+ System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+ mKeys = nkeys;
+ mValues = nvalues;
+ }
+}
diff --git a/core/java/android/util/Pools.java b/core/java/android/util/Pools.java
index 8edb3e6..70581be 100644
--- a/core/java/android/util/Pools.java
+++ b/core/java/android/util/Pools.java
@@ -17,25 +17,149 @@
package android.util;
/**
+ * Helper class for crating pools of objects. An example use looks like this:
+ * <pre>
+ * public class MyPooledClass {
+ *
+ * private static final SynchronizedPool<MyPooledClass> sPool =
+ * new SynchronizedPool<MyPooledClass>(10);
+ *
+ * public static MyPooledClass obtain() {
+ * MyPooledClass instance = sPool.acquire();
+ * return (instance != null) ? instance : new MyPooledClass();
+ * }
+ *
+ * public void recycle() {
+ * // Clear state if needed.
+ * sPool.release(this);
+ * }
+ *
+ * . . .
+ * }
+ * </pre>
+ *
* @hide
*/
-public class Pools {
- private Pools() {
- }
+public final class Pools {
+
+ /**
+ * Interface for managing a pool of objects.
+ *
+ * @param <T> The pooled type.
+ */
+ public static interface Pool<T> {
- public static <T extends Poolable<T>> Pool<T> simplePool(PoolableManager<T> manager) {
- return new FinitePool<T>(manager);
+ /**
+ * @return An instance from the pool if such, null otherwise.
+ */
+ public T acquire();
+
+ /**
+ * Release an instance to the pool.
+ *
+ * @param instance The instance to release.
+ * @return Whether the instance was put in the pool.
+ *
+ * @throws IllegalStateException If the instance is already in the pool.
+ */
+ public boolean release(T instance);
}
-
- public static <T extends Poolable<T>> Pool<T> finitePool(PoolableManager<T> manager, int limit) {
- return new FinitePool<T>(manager, limit);
+
+ private Pools() {
+ /* do nothing - hiding constructor */
}
- public static <T extends Poolable<T>> Pool<T> synchronizedPool(Pool<T> pool) {
- return new SynchronizedPool<T>(pool);
+ /**
+ * Simple (non-synchronized) pool of objects.
+ *
+ * @param <T> The pooled type.
+ */
+ public static class SimplePool<T> implements Pool<T> {
+ private final Object[] mPool;
+
+ private int mPoolSize;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param maxPoolSize The max pool size.
+ *
+ * @throws IllegalArgumentException If the max pool size is less than zero.
+ */
+ public SimplePool(int maxPoolSize) {
+ if (maxPoolSize <= 0) {
+ throw new IllegalArgumentException("The max pool size must be > 0");
+ }
+ mPool = new Object[maxPoolSize];
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T acquire() {
+ if (mPoolSize > 0) {
+ final int lastPooledIndex = mPoolSize - 1;
+ T instance = (T) mPool[lastPooledIndex];
+ mPool[lastPooledIndex] = null;
+ mPoolSize--;
+ return instance;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean release(T instance) {
+ if (isInPool(instance)) {
+ throw new IllegalStateException("Already in the pool!");
+ }
+ if (mPoolSize < mPool.length) {
+ mPool[mPoolSize] = instance;
+ mPoolSize++;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isInPool(T instance) {
+ for (int i = 0; i < mPoolSize; i++) {
+ if (mPool[i] == instance) {
+ return true;
+ }
+ }
+ return false;
+ }
}
- public static <T extends Poolable<T>> Pool<T> synchronizedPool(Pool<T> pool, Object lock) {
- return new SynchronizedPool<T>(pool, lock);
+ /**
+ * Synchronized) pool of objects.
+ *
+ * @param <T> The pooled type.
+ */
+ public static class SynchronizedPool<T> extends SimplePool<T> {
+ private final Object mLock = new Object();
+
+ /**
+ * Creates a new instance.
+ *
+ * @param maxPoolSize The max pool size.
+ *
+ * @throws IllegalArgumentException If the max pool size is less than zero.
+ */
+ public SynchronizedPool(int maxPoolSize) {
+ super(maxPoolSize);
+ }
+
+ @Override
+ public T acquire() {
+ synchronized (mLock) {
+ return super.acquire();
+ }
+ }
+
+ @Override
+ public boolean release(T element) {
+ synchronized (mLock) {
+ return super.release(element);
+ }
+ }
}
}
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index a08d5cb..2f7a6fe 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -22,8 +22,6 @@ import com.android.internal.util.ArrayUtils;
* SparseLongArrays map integers to longs. Unlike a normal array of longs,
* there can be gaps in the indices. It is intended to be more efficient
* than using a HashMap to map Integers to Longs.
- *
- * @hide
*/
public class SparseLongArray implements Cloneable {
diff --git a/core/java/android/util/SynchronizedPool.java b/core/java/android/util/SynchronizedPool.java
deleted file mode 100644
index 651e0c3..0000000
--- a/core/java/android/util/SynchronizedPool.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-/**
- *
- * @hide
- */
-class SynchronizedPool<T extends Poolable<T>> implements Pool<T> {
- private final Pool<T> mPool;
- private final Object mLock;
-
- public SynchronizedPool(Pool<T> pool) {
- mPool = pool;
- mLock = this;
- }
-
- public SynchronizedPool(Pool<T> pool, Object lock) {
- mPool = pool;
- mLock = lock;
- }
-
- public T acquire() {
- synchronized (mLock) {
- return mPool.acquire();
- }
- }
-
- public void release(T element) {
- synchronized (mLock) {
- mPool.release(element);
- }
- }
-}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 9bee4bf..2d6453e 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -16,8 +16,7 @@
package android.view;
-import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
-
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
@@ -26,12 +25,14 @@ import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.util.SparseLongArray;
+import android.view.View.AttachInfo;
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 com.android.internal.util.Predicate;
import java.util.ArrayList;
import java.util.HashMap;
@@ -47,7 +48,7 @@ import java.util.Map;
*/
final class AccessibilityInteractionController {
- private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
+ private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
new ArrayList<AccessibilityNodeInfo>();
private final Handler mHandler;
@@ -62,7 +63,12 @@ final class AccessibilityInteractionController {
private final ArrayList<View> mTempArrayList = new ArrayList<View>();
+ private final Point mTempPoint = new Point();
private final Rect mTempRect = new Rect();
+ private final Rect mTempRect1 = new Rect();
+ private final Rect mTempRect2 = new Rect();
+
+ private AddNodeInfosForViewId mAddNodeInfosForViewId;
public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
Looper looper = viewRootImpl.mHandler.getLooper();
@@ -86,7 +92,7 @@ final class AccessibilityInteractionController {
public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid) {
+ long interrogatingTid, MagnificationSpec spec) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
message.arg1 = flags;
@@ -96,6 +102,7 @@ final class AccessibilityInteractionController {
args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
args.argi3 = interactionId;
args.arg1 = callback;
+ args.arg2 = spec;
message.obj = args;
// If the interrogation is performed by the same thread as the main UI
@@ -119,6 +126,7 @@ final class AccessibilityInteractionController {
final int interactionId = args.argi3;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg2;
args.recycle();
@@ -128,8 +136,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
root = mViewRootImpl.mView;
@@ -141,8 +148,11 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
- applyApplicationScaleIfNeeded(infos);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
+ if (spec != null) {
+ spec.recycle();
+ }
callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
infos.clear();
} catch (RemoteException re) {
@@ -151,18 +161,19 @@ final class AccessibilityInteractionController {
}
}
- public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
- int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int flags, int interrogatingPid, long interrogatingTid) {
+ public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
+ String viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
Message message = mHandler.obtainMessage();
- message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID;
message.arg1 = flags;
message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
SomeArgs args = SomeArgs.obtain();
- args.argi1 = viewId;
- args.argi2 = interactionId;
+ args.argi1 = interactionId;
args.arg1 = callback;
+ args.arg2 = spec;
+ args.arg3 = viewId;
message.obj = args;
@@ -178,25 +189,26 @@ final class AccessibilityInteractionController {
}
}
- private void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
+ private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
final int flags = message.arg1;
final int accessibilityViewId = message.arg2;
SomeArgs args = (SomeArgs) message.obj;
- final int viewId = args.argi1;
- final int interactionId = args.argi2;
+ final int interactionId = args.argi1;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg2;
+ final String viewId = (String) args.arg3;
args.recycle();
- AccessibilityNodeInfo info = null;
+ final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
try {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -204,16 +216,26 @@ final class AccessibilityInteractionController {
root = mViewRootImpl.mView;
}
if (root != null) {
- View target = root.findViewById(viewId);
- if (target != null && isShown(target)) {
- info = target.createAccessibilityNodeInfo();
+ final int resolvedViewId = root.getContext().getResources()
+ .getIdentifier(viewId, null, null);
+ if (resolvedViewId <= 0) {
+ return;
+ }
+ if (mAddNodeInfosForViewId == null) {
+ mAddNodeInfosForViewId = new AddNodeInfosForViewId();
}
+ mAddNodeInfosForViewId.init(resolvedViewId, infos);
+ root.findViewByPredicate(mAddNodeInfosForViewId);
+ mAddNodeInfosForViewId.reset();
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
- applyApplicationScaleIfNeeded(info);
- callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
+ if (spec != null) {
+ spec.recycle();
+ }
+ callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
}
@@ -222,7 +244,7 @@ final class AccessibilityInteractionController {
public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int flags, int interrogatingPid, long interrogatingTid) {
+ int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
message.arg1 = flags;
@@ -230,10 +252,10 @@ final class AccessibilityInteractionController {
SomeArgs args = SomeArgs.obtain();
args.arg1 = text;
args.arg2 = callback;
+ args.arg3 = spec;
args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
args.argi3 = interactionId;
-
message.obj = args;
// If the interrogation is performed by the same thread as the main UI
@@ -255,6 +277,7 @@ final class AccessibilityInteractionController {
final String text = (String) args.arg1;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg2;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg3;
final int accessibilityViewId = args.argi1;
final int virtualDescendantId = args.argi2;
final int interactionId = args.argi3;
@@ -265,8 +288,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -309,8 +331,11 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
- applyApplicationScaleIfNeeded(infos);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
+ if (spec != null) {
+ spec.recycle();
+ }
callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -320,7 +345,7 @@ final class AccessibilityInteractionController {
public void findFocusClientThread(long accessibilityNodeId, int focusType, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
- long interrogatingTid) {
+ long interrogatingTid, MagnificationSpec spec) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FIND_FOCUS;
message.arg1 = flags;
@@ -331,6 +356,7 @@ final class AccessibilityInteractionController {
args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
args.arg1 = callback;
+ args.arg2 = spec;
message.obj = args;
@@ -356,7 +382,7 @@ final class AccessibilityInteractionController {
final int virtualDescendantId = args.argi3;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
-
+ final MagnificationSpec spec = (MagnificationSpec) args.arg2;
args.recycle();
AccessibilityNodeInfo focused = null;
@@ -364,8 +390,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -406,8 +431,11 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
- applyApplicationScaleIfNeeded(focused);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(focused, spec);
+ if (spec != null) {
+ spec.recycle();
+ }
callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -417,7 +445,7 @@ final class AccessibilityInteractionController {
public void focusSearchClientThread(long accessibilityNodeId, int direction, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
- long interrogatingTid) {
+ long interrogatingTid, MagnificationSpec spec) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FOCUS_SEARCH;
message.arg1 = flags;
@@ -427,6 +455,7 @@ final class AccessibilityInteractionController {
args.argi2 = direction;
args.argi3 = interactionId;
args.arg1 = callback;
+ args.arg2 = spec;
message.obj = args;
@@ -451,6 +480,7 @@ final class AccessibilityInteractionController {
final int interactionId = args.argi3;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg2;
args.recycle();
@@ -459,8 +489,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -475,8 +504,11 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
- applyApplicationScaleIfNeeded(next);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(next, spec);
+ if (spec != null) {
+ spec.recycle();
+ }
callback.setFindAccessibilityNodeInfoResult(next, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -533,8 +565,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View target = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
target = findViewByAccessibilityId(accessibilityViewId);
@@ -552,7 +583,7 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
callback.setPerformAccessibilityActionResult(succeeded, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -572,38 +603,84 @@ final class AccessibilityInteractionController {
return foundView;
}
- private void applyApplicationScaleIfNeeded(List<AccessibilityNodeInfo> infos) {
+ private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
+ MagnificationSpec spec) {
if (infos == null) {
return;
}
final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
- if (applicationScale != 1.0f) {
+ if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
final int infoCount = infos.size();
for (int i = 0; i < infoCount; i++) {
AccessibilityNodeInfo info = infos.get(i);
- applyApplicationScaleIfNeeded(info);
+ applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
}
}
}
- private void applyApplicationScaleIfNeeded(AccessibilityNodeInfo info) {
+ private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
+ MagnificationSpec spec) {
if (info == null) {
return;
}
+
final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
+ if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
+ return;
+ }
+
+ Rect boundsInParent = mTempRect;
+ Rect boundsInScreen = mTempRect1;
+
+ info.getBoundsInParent(boundsInParent);
+ info.getBoundsInScreen(boundsInScreen);
if (applicationScale != 1.0f) {
- Rect bounds = mTempRect;
+ boundsInParent.scale(applicationScale);
+ boundsInScreen.scale(applicationScale);
+ }
+ if (spec != null) {
+ boundsInParent.scale(spec.scale);
+ // boundsInParent must not be offset.
+ boundsInScreen.scale(spec.scale);
+ boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
+ }
+ info.setBoundsInParent(boundsInParent);
+ info.setBoundsInScreen(boundsInScreen);
+
+ if (spec != null) {
+ AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
+ if (attachInfo.mDisplay == null) {
+ return;
+ }
- info.getBoundsInParent(bounds);
- bounds.scale(applicationScale);
- info.setBoundsInParent(bounds);
+ final float scale = attachInfo.mApplicationScale * spec.scale;
- info.getBoundsInScreen(bounds);
- bounds.scale(applicationScale);
- info.setBoundsInScreen(bounds);
+ Rect visibleWinFrame = mTempRect1;
+ visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
+ visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
+ visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
+ visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
+
+ attachInfo.mDisplay.getRealSize(mTempPoint);
+ final int displayWidth = mTempPoint.x;
+ final int displayHeight = mTempPoint.y;
+
+ Rect visibleDisplayFrame = mTempRect2;
+ visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
+
+ visibleWinFrame.intersect(visibleDisplayFrame);
+
+ if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
+ boundsInScreen.right, boundsInScreen.bottom)) {
+ info.setVisibleToUser(false);
+ }
}
}
+ private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
+ MagnificationSpec spec) {
+ return (appScale != 1.0f || (spec != null && !spec.isNop()));
+ }
/**
* This class encapsulates a prefetching strategy for the accessibility APIs for
@@ -616,20 +693,20 @@ final class AccessibilityInteractionController {
private final ArrayList<View> mTempViewList = new ArrayList<View>();
- public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
+ public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
List<AccessibilityNodeInfo> outInfos) {
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
if (provider == null) {
AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
if (root != null) {
outInfos.add(root);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
prefetchPredecessorsOfRealNode(view, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
prefetchSiblingsOfRealNode(view, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
prefetchDescendantsOfRealNode(view, outInfos);
}
}
@@ -637,13 +714,13 @@ final class AccessibilityInteractionController {
AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
if (root != null) {
outInfos.add(root);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
prefetchDescendantsOfVirtualNode(root, provider, outInfos);
}
}
@@ -846,7 +923,7 @@ final class AccessibilityInteractionController {
private class PrivateHandler extends Handler {
private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
- private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID = 3;
private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
private final static int MSG_FIND_FOCUS = 5;
private final static int MSG_FOCUS_SEARCH = 6;
@@ -863,8 +940,8 @@ final class AccessibilityInteractionController {
return "MSG_PERFORM_ACCESSIBILITY_ACTION";
case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
- return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID";
case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
case MSG_FIND_FOCUS:
@@ -886,8 +963,8 @@ final class AccessibilityInteractionController {
case MSG_PERFORM_ACCESSIBILITY_ACTION: {
perfromAccessibilityActionUiThread(message);
} break;
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
- findAccessibilityNodeInfoByViewIdUiThread(message);
+ case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID: {
+ findAccessibilityNodeInfosByViewIdUiThread(message);
} break;
case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
findAccessibilityNodeInfosByTextUiThread(message);
@@ -903,4 +980,27 @@ final class AccessibilityInteractionController {
}
}
}
+
+ private final class AddNodeInfosForViewId implements Predicate<View> {
+ private int mViewId = View.NO_ID;
+ private List<AccessibilityNodeInfo> mInfos;
+
+ public void init(int viewId, List<AccessibilityNodeInfo> infos) {
+ mViewId = viewId;
+ mInfos = infos;
+ }
+
+ public void reset() {
+ mViewId = View.NO_ID;
+ mInfos = null;
+ }
+
+ @Override
+ public boolean apply(View view) {
+ if (view.getId() == mViewId && isShown(view)) {
+ mInfos.add(view.createAccessibilityNodeInfo());
+ }
+ return false;
+ }
+ }
}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index b661748..f28e4b5 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -693,7 +693,7 @@ public final class Choreographer {
// 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) {
+ if (builtInDisplayId != SurfaceControl.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 "
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 758abb5..e6a7950 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -437,6 +437,20 @@ public final class Display {
}
/**
+ * @hide
+ * Return a rectangle defining the insets of the overscan region of the display.
+ * Each field of the rectangle is the number of pixels the overscan area extends
+ * into the display on that side.
+ */
+ public void getOverscanInsets(Rect outRect) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ outRect.set(mDisplayInfo.overscanLeft, mDisplayInfo.overscanTop,
+ mDisplayInfo.overscanRight, mDisplayInfo.overscanBottom);
+ }
+ }
+
+ /**
* 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},
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index a919ffc..4dade20 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -102,7 +102,7 @@ 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}.
+ * {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}.
* @param frame The frame number. Increases by one for each vertical sync interval.
*/
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
@@ -114,7 +114,7 @@ public abstract class DisplayEventReceiver {
* @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}.
+ * {@link SurfaceControl#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) {
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 305fd5c..9fcd9b1 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -109,6 +109,30 @@ public final class DisplayInfo implements Parcelable {
public int logicalHeight;
/**
+ * @hide
+ * Number of overscan pixels on the left side of the display.
+ */
+ public int overscanLeft;
+
+ /**
+ * @hide
+ * Number of overscan pixels on the top side of the display.
+ */
+ public int overscanTop;
+
+ /**
+ * @hide
+ * Number of overscan pixels on the right side of the display.
+ */
+ public int overscanRight;
+
+ /**
+ * @hide
+ * Number of overscan pixels on the bottom side of the display.
+ */
+ public int overscanBottom;
+
+ /**
* 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},
@@ -196,6 +220,10 @@ public final class DisplayInfo implements Parcelable {
&& largestNominalAppHeight == other.largestNominalAppHeight
&& logicalWidth == other.logicalWidth
&& logicalHeight == other.logicalHeight
+ && overscanLeft == other.overscanLeft
+ && overscanTop == other.overscanTop
+ && overscanRight == other.overscanRight
+ && overscanBottom == other.overscanBottom
&& rotation == other.rotation
&& refreshRate == other.refreshRate
&& logicalDensityDpi == other.logicalDensityDpi
@@ -222,6 +250,10 @@ public final class DisplayInfo implements Parcelable {
largestNominalAppHeight = other.largestNominalAppHeight;
logicalWidth = other.logicalWidth;
logicalHeight = other.logicalHeight;
+ overscanLeft = other.overscanLeft;
+ overscanTop = other.overscanTop;
+ overscanRight = other.overscanRight;
+ overscanBottom = other.overscanBottom;
rotation = other.rotation;
refreshRate = other.refreshRate;
logicalDensityDpi = other.logicalDensityDpi;
@@ -243,6 +275,10 @@ public final class DisplayInfo implements Parcelable {
largestNominalAppHeight = source.readInt();
logicalWidth = source.readInt();
logicalHeight = source.readInt();
+ overscanLeft = source.readInt();
+ overscanTop = source.readInt();
+ overscanRight = source.readInt();
+ overscanBottom = source.readInt();
rotation = source.readInt();
refreshRate = source.readFloat();
logicalDensityDpi = source.readInt();
@@ -265,6 +301,10 @@ public final class DisplayInfo implements Parcelable {
dest.writeInt(largestNominalAppHeight);
dest.writeInt(logicalWidth);
dest.writeInt(logicalHeight);
+ dest.writeInt(overscanLeft);
+ dest.writeInt(overscanTop);
+ dest.writeInt(overscanRight);
+ dest.writeInt(overscanBottom);
dest.writeInt(rotation);
dest.writeFloat(refreshRate);
dest.writeInt(logicalDensityDpi);
@@ -318,18 +358,55 @@ public final class DisplayInfo implements Parcelable {
// 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
- + ", type " + Display.typeToString(type)
- + ", address " + address
- + flagsToString(flags) + "}";
+ StringBuilder sb = new StringBuilder();
+ sb.append("DisplayInfo{\"");
+ sb.append(name);
+ sb.append("\", app ");
+ sb.append(appWidth);
+ sb.append(" x ");
+ sb.append(appHeight);
+ sb.append(", real ");
+ sb.append(logicalWidth);
+ sb.append(" x ");
+ sb.append(logicalHeight);
+ if (overscanLeft != 0 || overscanTop != 0 || overscanRight != 0 || overscanBottom != 0) {
+ sb.append(", overscan (");
+ sb.append(overscanLeft);
+ sb.append(",");
+ sb.append(overscanTop);
+ sb.append(",");
+ sb.append(overscanRight);
+ sb.append(",");
+ sb.append(overscanBottom);
+ sb.append(")");
+ }
+ sb.append(", largest app ");
+ sb.append(largestNominalAppWidth);
+ sb.append(" x ");
+ sb.append(largestNominalAppHeight);
+ sb.append(", smallest app ");
+ sb.append(smallestNominalAppWidth);
+ sb.append(" x ");
+ sb.append(smallestNominalAppHeight);
+ sb.append(", ");
+ sb.append(refreshRate);
+ sb.append(" fps, rotation");
+ sb.append(rotation);
+ sb.append(", density ");
+ sb.append(logicalDensityDpi);
+ sb.append(" (");
+ sb.append(physicalXDpi);
+ sb.append(" x ");
+ sb.append(physicalYDpi);
+ sb.append(") dpi, layerStack ");
+ sb.append(layerStack);
+ sb.append(", type ");
+ sb.append(Display.typeToString(type));
+ sb.append(", address ");
+ sb.append(address);
+ sb.append(flagsToString(flags));
+ sb.append("}");
+ return sb.toString();
}
private static String flagsToString(int flags) {
diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java
index 5e34a36..2d24c1e 100644
--- a/core/java/android/view/DisplayList.java
+++ b/core/java/android/view/DisplayList.java
@@ -19,21 +19,121 @@ package android.view;
import android.graphics.Matrix;
/**
- * A display lists records a series of graphics related operation and can replay
+ * <p>A display list records a series of graphics related operations and can replay
* them later. Display lists are usually built by recording operations on a
- * {@link android.graphics.Canvas}. Replaying the operations from a display list
- * avoids executing views drawing code on every frame, and is thus much more
- * efficient.
+ * {@link HardwareCanvas}. Replaying the operations from a display list avoids
+ * executing application code on every frame, and is thus much more efficient.</p>
*
- * @hide
+ * <p>Display lists are used internally for all views by default, and are not
+ * typically used directly. One reason to consider using a display is a custom
+ * {@link View} implementation that needs to issue a large number of drawing commands.
+ * When the view invalidates, all the drawing commands must be reissued, even if
+ * large portions of the drawing command stream stay the same frame to frame, which
+ * can become a performance bottleneck. To solve this issue, a custom View might split
+ * its content into several display lists. A display list is updated only when its
+ * content, and only its content, needs to be updated.</p>
+ *
+ * <p>A text editor might for instance store each paragraph into its own display list.
+ * Thus when the user inserts or removes characters, only the display list of the
+ * affected paragraph needs to be recorded again.</p>
+ *
+ * <h3>Hardware acceleration</h3>
+ * <p>Display lists can only be replayed using a {@link HardwareCanvas}. They are not
+ * supported in software. Always make sure that the {@link android.graphics.Canvas}
+ * you are using to render a display list is hardware accelerated using
+ * {@link android.graphics.Canvas#isHardwareAccelerated()}.</p>
+ *
+ * <h3>Creating a display list</h3>
+ * <pre class="prettyprint">
+ * HardwareRenderer renderer = myView.getHardwareRenderer();
+ * if (renderer != null) {
+ * DisplayList displayList = renderer.createDisplayList();
+ * HardwareCanvas canvas = displayList.start(width, height);
+ * try {
+ * // Draw onto the canvas
+ * // For instance: canvas.drawBitmap(...);
+ * } finally {
+ * displayList.end();
+ * }
+ * }
+ * </pre>
+ *
+ * <h3>Rendering a display list on a View</h3>
+ * <pre class="prettyprint">
+ * protected void onDraw(Canvas canvas) {
+ * if (canvas.isHardwareAccelerated()) {
+ * HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas;
+ * hardwareCanvas.drawDisplayList(mDisplayList);
+ * }
+ * }
+ * </pre>
+ *
+ * <h3>Releasing resources</h3>
+ * <p>This step is not mandatory but recommended if you want to release resources
+ * held by a display list as soon as possible.</p>
+ * <pre class="prettyprint">
+ * // Mark this display list invalid, it cannot be used for drawing anymore,
+ * // and release resources held by this display list
+ * displayList.clear();
+ * </pre>
+ *
+ * <h3>Properties</h3>
+ * <p>In addition, a display list offers several properties, such as
+ * {@link #setScaleX(float)} or {@link #setLeft(int)}, that can be used to affect all
+ * the drawing commands recorded within. For instance, these properties can be used
+ * to move around a large number of images without re-issuing all the individual
+ * <code>drawBitmap()</code> calls.</p>
+ *
+ * <pre class="prettyprint">
+ * private void createDisplayList() {
+ * HardwareRenderer renderer = getHardwareRenderer();
+ * if (renderer != null) {
+ * mDisplayList = renderer.createDisplayList();
+ * HardwareCanvas canvas = mDisplayList.start(width, height);
+ * try {
+ * for (Bitmap b : mBitmaps) {
+ * canvas.drawBitmap(b, 0.0f, 0.0f, null);
+ * canvas.translate(0.0f, b.getHeight());
+ * }
+ * } finally {
+ * displayList.end();
+ * }
+ * }
+ * }
+ *
+ * protected void onDraw(Canvas canvas) {
+ * if (canvas.isHardwareAccelerated()) {
+ * HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas;
+ * hardwareCanvas.drawDisplayList(mDisplayList);
+ * }
+ * }
+ *
+ * private void moveContentBy(int x) {
+ * // This will move all the bitmaps recorded inside the display list
+ * // by x pixels to the right and redraw this view. All the commands
+ * // recorded in createDisplayList() won't be re-issued, only onDraw()
+ * // will be invoked and will execute very quickly
+ * mDisplayList.offsetLeftAndRight(x);
+ * invalidate();
+ * }
+ * </pre>
+ *
+ * <h3>Threading</h3>
+ * <p>Display lists must be created on and manipulated from the UI thread only.</p>
+ *
+ * @hide
*/
public abstract class DisplayList {
+ private boolean mDirty;
+
/**
* Flag used when calling
* {@link HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)}
* When this flag is set, draw operations lying outside of the bounds of the
* display list will be culled early. It is recommeneded to always set this
* flag.
+ *
+ * @hide
*/
public static final int FLAG_CLIP_CHILDREN = 0x1;
@@ -42,14 +142,18 @@ public abstract class DisplayList {
/**
* Indicates that the display list is done drawing.
*
- * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
+ * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
+ *
+ * @hide
*/
public static final int STATUS_DONE = 0x0;
/**
* Indicates that the display list needs another drawing pass.
*
- * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
+ * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
+ *
+ * @hide
*/
public static final int STATUS_DRAW = 0x1;
@@ -57,7 +161,9 @@ public abstract class DisplayList {
* Indicates that the display list needs to re-execute its GL functors.
*
* @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
- * @see HardwareCanvas#callDrawGLFunction(int)
+ * @see HardwareCanvas#callDrawGLFunction(int)
+ *
+ * @hide
*/
public static final int STATUS_INVOKE = 0x2;
@@ -65,35 +171,83 @@ public abstract class DisplayList {
* Indicates that the display list performed GL drawing operations.
*
* @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
+ *
+ * @hide
*/
public static final int STATUS_DREW = 0x4;
/**
* Starts recording the display list. All operations performed on the
* returned canvas are recorded and stored in this display list.
- *
+ *
+ * Calling this method will mark the display list invalid until
+ * {@link #end()} is called. Only valid display lists can be replayed.
+ *
+ * @param width The width of the display list's viewport
+ * @param height The height of the display list's viewport
+ *
* @return A canvas to record drawing operations.
+ *
+ * @see #end()
+ * @see #isValid()
*/
- public abstract HardwareCanvas start();
+ public abstract HardwareCanvas start(int width, int height);
/**
* Ends the recording for this display list. A display list cannot be
- * replayed if recording is not finished.
+ * replayed if recording is not finished. Calling this method marks
+ * the display list valid and {@link #isValid()} will return true.
+ *
+ * @see #start(int, int)
+ * @see #isValid()
*/
public abstract void end();
/**
- * Invalidates the display list, indicating that it should be repopulated
- * with new drawing commands prior to being used again. Calling this method
- * causes calls to {@link #isValid()} to return <code>false</code>.
+ * Clears resources held onto by this display list. After calling this method
+ * {@link #isValid()} will return false.
+ *
+ * @see #isValid()
+ */
+ public abstract void clear();
+
+ /**
+ * Sets the dirty flag. When a display list is dirty, {@link #clear()} should
+ * be invoked whenever possible.
+ *
+ * @see #isDirty()
+ * @see #clear()
+ *
+ * @hide
*/
- public abstract void invalidate();
+ public void markDirty() {
+ mDirty = true;
+ }
/**
- * Clears additional resources held onto by this display list. You should
- * only invoke this method after {@link #invalidate()}.
+ * Removes the dirty flag. This method can be used to cancel a cleanup
+ * previously scheduled by setting the dirty flag.
+ *
+ * @see #isDirty()
+ * @see #clear()
+ *
+ * @hide
*/
- public abstract void clear();
+ protected void clearDirty() {
+ mDirty = false;
+ }
+
+ /**
+ * Indicates whether the display list is dirty.
+ *
+ * @see #markDirty()
+ * @see #clear()
+ *
+ * @hide
+ */
+ public boolean isDirty() {
+ return mDirty;
+ }
/**
* Returns whether the display list is currently usable. If this returns false,
@@ -107,6 +261,8 @@ public abstract class DisplayList {
* Return the amount of memory used by this display list.
*
* @return The size of this display list in bytes
+ *
+ * @hide
*/
public abstract int getSize();
@@ -115,228 +271,412 @@ public abstract class DisplayList {
///////////////////////////////////////////////////////////////////////////
/**
- * Set the caching property on the DisplayList, which indicates whether the DisplayList
- * holds a layer. Layer DisplayLists should avoid creating an alpha layer, since alpha is
+ * Set the caching property on the display list, which indicates whether the display list
+ * holds a layer. Layer display lists should avoid creating an alpha layer, since alpha is
* handled in the drawLayer operation directly (and more efficiently).
*
- * @param caching true if the DisplayList represents a hardware layer, false otherwise.
+ * @param caching true if the display list represents a hardware layer, false otherwise.
+ *
+ * @hide
*/
public abstract void setCaching(boolean caching);
/**
- * Set whether the DisplayList should clip itself to its bounds. This property is controlled by
+ * Set whether the display list should clip itself to its bounds. This property is controlled by
* the view's parent.
*
- * @param clipChildren true if the DisplayList should clip to its bounds
+ * @param clipToBounds true if the display list should clip to its bounds
+ */
+ public abstract void setClipToBounds(boolean clipToBounds);
+
+ /**
+ * Set the static matrix on the display list. The specified matrix is combined with other
+ * transforms (such as {@link #setScaleX(float)}, {@link #setRotation(float)}, etc.)
+ *
+ * @param matrix A transform matrix to apply to this display list
+ *
+ * @see #getMatrix(android.graphics.Matrix)
+ * @see #getMatrix()
*/
- public abstract void setClipChildren(boolean clipChildren);
+ public abstract void setMatrix(Matrix matrix);
/**
- * Set the static matrix on the DisplayList. This matrix exists if a custom ViewGroup
- * overrides
- * {@link ViewGroup#getChildStaticTransformation(View, android.view.animation.Transformation)}
- * and also has {@link ViewGroup#setStaticTransformationsEnabled(boolean)} set to true.
- * This matrix will be concatenated with any other matrices in the DisplayList to position
- * the view appropriately.
+ * Returns the static matrix set on this display list.
+ *
+ * @return A new {@link Matrix} instance populated with this display list's static
+ * matrix
+ *
+ * @see #getMatrix(android.graphics.Matrix)
+ * @see #setMatrix(android.graphics.Matrix)
+ */
+ public Matrix getMatrix() {
+ return getMatrix(new Matrix());
+ }
+
+ /**
+ * Copies this display list's static matrix into the specified matrix.
+ *
+ * @param matrix The {@link Matrix} instance in which to copy this display
+ * list's static matrix. Cannot be null
*
- * @param matrix The matrix
+ * @return The <code>matrix</code> parameter, for convenience
+ *
+ * @see #getMatrix()
+ * @see #setMatrix(android.graphics.Matrix)
*/
- public abstract void setStaticMatrix(Matrix matrix);
+ public abstract Matrix getMatrix(Matrix matrix);
/**
- * Set the Animation matrix on the DisplayList. This matrix exists if an Animation is
- * currently playing on a View, and is set on the DisplayList during at draw() time. When
+ * Set the Animation matrix on the display list. This matrix exists if an Animation is
+ * currently playing on a View, and is set on the display list during at draw() time. When
* the Animation finishes, the matrix should be cleared by sending <code>null</code>
* for the matrix parameter.
*
* @param matrix The matrix, null indicates that the matrix should be cleared.
+ *
+ * @hide
*/
public abstract void setAnimationMatrix(Matrix matrix);
/**
- * Sets the alpha value for the DisplayList
+ * Sets the translucency level for the display list.
+ *
+ * @param alpha The translucency of the display list, must be a value between 0.0f and 1.0f
*
- * @param alpha The translucency of the DisplayList
* @see View#setAlpha(float)
+ * @see #getAlpha()
*/
public abstract void setAlpha(float alpha);
/**
- * Sets whether the DisplayList renders content which overlaps. Non-overlapping rendering
- * can use a fast path for alpha that avoids rendering to an offscreen buffer.
+ * Returns the translucency level of this display list.
+ *
+ * @return A value between 0.0f and 1.0f
+ *
+ * @see #setAlpha(float)
+ */
+ public abstract float getAlpha();
+
+ /**
+ * Sets whether the display list renders content which overlaps. Non-overlapping rendering
+ * can use a fast path for alpha that avoids rendering to an offscreen buffer. By default
+ * display lists consider they do not have overlapping content.
+ *
+ * @param hasOverlappingRendering False if the content is guaranteed to be non-overlapping,
+ * true otherwise.
*
- * @param hasOverlappingRendering
* @see android.view.View#hasOverlappingRendering()
+ * @see #hasOverlappingRendering()
*/
public abstract void setHasOverlappingRendering(boolean hasOverlappingRendering);
/**
- * Sets the translationX value for the DisplayList
+ * Indicates whether the content of this display list overlaps.
+ *
+ * @return True if this display list renders content which overlaps, false otherwise.
+ *
+ * @see #setHasOverlappingRendering(boolean)
+ */
+ public abstract boolean hasOverlappingRendering();
+
+ /**
+ * Sets the translation value for the display list on the X axis
+ *
+ * @param translationX The X axis translation value of the display list, in pixels
*
- * @param translationX The translationX value of the DisplayList
* @see View#setTranslationX(float)
+ * @see #getTranslationX()
*/
public abstract void setTranslationX(float translationX);
/**
- * Sets the translationY value for the DisplayList
+ * Returns the translation value for this display list on the X axis, in pixels.
+ *
+ * @see #setTranslationX(float)
+ */
+ public abstract float getTranslationX();
+
+ /**
+ * Sets the translation value for the display list on the Y axis
+ *
+ * @param translationY The Y axis translation value of the display list, in pixels
*
- * @param translationY The translationY value of the DisplayList
* @see View#setTranslationY(float)
+ * @see #getTranslationY()
*/
public abstract void setTranslationY(float translationY);
/**
- * Sets the rotation value for the DisplayList
+ * Returns the translation value for this display list on the Y axis, in pixels.
+ *
+ * @see #setTranslationY(float)
+ */
+ public abstract float getTranslationY();
+
+ /**
+ * Sets the rotation value for the display list around the Z axis
+ *
+ * @param rotation The rotation value of the display list, in degrees
*
- * @param rotation The rotation value of the DisplayList
* @see View#setRotation(float)
+ * @see #getRotation()
*/
public abstract void setRotation(float rotation);
/**
- * Sets the rotationX value for the DisplayList
+ * Returns the rotation value for this display list around the Z axis, in degrees.
+ *
+ * @see #setRotation(float)
+ */
+ public abstract float getRotation();
+
+ /**
+ * Sets the rotation value for the display list around the X axis
+ *
+ * @param rotationX The rotation value of the display list, in degrees
*
- * @param rotationX The rotationX value of the DisplayList
* @see View#setRotationX(float)
+ * @see #getRotationX()
*/
public abstract void setRotationX(float rotationX);
/**
- * Sets the rotationY value for the DisplayList
+ * Returns the rotation value for this display list around the X axis, in degrees.
+ *
+ * @see #setRotationX(float)
+ */
+ public abstract float getRotationX();
+
+ /**
+ * Sets the rotation value for the display list around the Y axis
+ *
+ * @param rotationY The rotation value of the display list, in degrees
*
- * @param rotationY The rotationY value of the DisplayList
* @see View#setRotationY(float)
+ * @see #getRotationY()
*/
public abstract void setRotationY(float rotationY);
/**
- * Sets the scaleX value for the DisplayList
+ * Returns the rotation value for this display list around the Y axis, in degrees.
+ *
+ * @see #setRotationY(float)
+ */
+ public abstract float getRotationY();
+
+ /**
+ * Sets the scale value for the display list on the X axis
+ *
+ * @param scaleX The scale value of the display list
*
- * @param scaleX The scaleX value of the DisplayList
* @see View#setScaleX(float)
+ * @see #getScaleX()
*/
public abstract void setScaleX(float scaleX);
/**
- * Sets the scaleY value for the DisplayList
+ * Returns the scale value for this display list on the X axis.
+ *
+ * @see #setScaleX(float)
+ */
+ public abstract float getScaleX();
+
+ /**
+ * Sets the scale value for the display list on the Y axis
+ *
+ * @param scaleY The scale value of the display list
*
- * @param scaleY The scaleY value of the DisplayList
* @see View#setScaleY(float)
+ * @see #getScaleY()
*/
public abstract void setScaleY(float scaleY);
/**
- * Sets all of the transform-related values of the View onto the DisplayList
+ * Returns the scale value for this display list on the Y axis.
*
- * @param alpha The alpha value of the DisplayList
- * @param translationX The translationX value of the DisplayList
- * @param translationY The translationY value of the DisplayList
- * @param rotation The rotation value of the DisplayList
- * @param rotationX The rotationX value of the DisplayList
- * @param rotationY The rotationY value of the DisplayList
- * @param scaleX The scaleX value of the DisplayList
- * @param scaleY The scaleY value of the DisplayList
+ * @see #setScaleY(float)
+ */
+ public abstract float getScaleY();
+
+ /**
+ * Sets all of the transform-related values of the display list
+ *
+ * @param alpha The alpha value of the display list
+ * @param translationX The translationX value of the display list
+ * @param translationY The translationY value of the display list
+ * @param rotation The rotation value of the display list
+ * @param rotationX The rotationX value of the display list
+ * @param rotationY The rotationY value of the display list
+ * @param scaleX The scaleX value of the display list
+ * @param scaleY The scaleY value of the display list
+ *
+ * @hide
*/
public abstract void setTransformationInfo(float alpha, float translationX, float translationY,
float rotation, float rotationX, float rotationY, float scaleX, float scaleY);
/**
- * Sets the pivotX value for the DisplayList
+ * Sets the pivot value for the display list on the X axis
+ *
+ * @param pivotX The pivot value of the display list on the X axis, in pixels
*
- * @param pivotX The pivotX value of the DisplayList
* @see View#setPivotX(float)
+ * @see #getPivotX()
*/
public abstract void setPivotX(float pivotX);
/**
- * Sets the pivotY value for the DisplayList
+ * Returns the pivot value for this display list on the X axis, in pixels.
+ *
+ * @see #setPivotX(float)
+ */
+ public abstract float getPivotX();
+
+ /**
+ * Sets the pivot value for the display list on the Y axis
+ *
+ * @param pivotY The pivot value of the display list on the Y axis, in pixels
*
- * @param pivotY The pivotY value of the DisplayList
* @see View#setPivotY(float)
+ * @see #getPivotY()
*/
public abstract void setPivotY(float pivotY);
/**
- * Sets the camera distance for the DisplayList
+ * Returns the pivot value for this display list on the Y axis, in pixels.
+ *
+ * @see #setPivotY(float)
+ */
+ public abstract float getPivotY();
+
+ /**
+ * Sets the camera distance for the display list. Refer to
+ * {@link View#setCameraDistance(float)} for more information on how to
+ * use this property.
+ *
+ * @param distance The distance in Z of the camera of the display list
*
- * @param distance The distance in z of the camera of the DisplayList
* @see View#setCameraDistance(float)
+ * @see #getCameraDistance()
*/
public abstract void setCameraDistance(float distance);
/**
- * Sets the left value for the DisplayList
+ * Returns the distance in Z of the camera of the display list.
+ *
+ * @see #setCameraDistance(float)
+ */
+ public abstract float getCameraDistance();
+
+ /**
+ * Sets the left position for the display list.
+ *
+ * @param left The left position, in pixels, of the display list
*
- * @param left The left value of the DisplayList
* @see View#setLeft(int)
+ * @see #getLeft()
*/
public abstract void setLeft(int left);
/**
- * Sets the top value for the DisplayList
+ * Returns the left position for the display list in pixels.
+ *
+ * @see #setLeft(int)
+ */
+ public abstract float getLeft();
+
+ /**
+ * Sets the top position for the display list.
+ *
+ * @param top The top position, in pixels, of the display list
*
- * @param top The top value of the DisplayList
* @see View#setTop(int)
+ * @see #getTop()
*/
public abstract void setTop(int top);
/**
- * Sets the right value for the DisplayList
+ * Returns the top position for the display list in pixels.
+ *
+ * @see #setTop(int)
+ */
+ public abstract float getTop();
+
+ /**
+ * Sets the right position for the display list.
+ *
+ * @param right The right position, in pixels, of the display list
*
- * @param right The right value of the DisplayList
* @see View#setRight(int)
+ * @see #getRight()
*/
public abstract void setRight(int right);
/**
- * Sets the bottom value for the DisplayList
+ * Returns the right position for the display list in pixels.
+ *
+ * @see #setRight(int)
+ */
+ public abstract float getRight();
+
+ /**
+ * Sets the bottom position for the display list.
+ *
+ * @param bottom The bottom position, in pixels, of the display list
*
- * @param bottom The bottom value of the DisplayList
* @see View#setBottom(int)
+ * @see #getBottom()
*/
public abstract void setBottom(int bottom);
/**
- * Sets the left and top values for the DisplayList
+ * Returns the bottom position for the display list in pixels.
*
- * @param left The left value of the DisplayList
- * @param top The top value of the DisplayList
- * @see View#setLeft(int)
- * @see View#setTop(int)
+ * @see #setBottom(int)
*/
- public abstract void setLeftTop(int left, int top);
+ public abstract float getBottom();
/**
- * Sets the left and top values for the DisplayList
+ * Sets the left and top positions for the display list
+ *
+ * @param left The left position of the display list, in pixels
+ * @param top The top position of the display list, in pixels
+ * @param right The right position of the display list, in pixels
+ * @param bottom The bottom position of the display list, in pixels
*
- * @param left The left value of the DisplayList
- * @param top The top value of the DisplayList
* @see View#setLeft(int)
* @see View#setTop(int)
+ * @see View#setRight(int)
+ * @see View#setBottom(int)
*/
public abstract void setLeftTopRightBottom(int left, int top, int right, int bottom);
/**
- * Offsets the left and right values for the DisplayList
+ * Offsets the left and right positions for the display list
+ *
+ * @param offset The amount that the left and right positions of the display
+ * list are offset, in pixels
*
- * @param offset The amount that the left and right values of the DisplayList are offset
* @see View#offsetLeftAndRight(int)
*/
- public abstract void offsetLeftRight(int offset);
+ public abstract void offsetLeftAndRight(float offset);
/**
- * Offsets the top and bottom values for the DisplayList
+ * Offsets the top and bottom values for the display list
+ *
+ * @param offset The amount that the top and bottom positions of the display
+ * list are offset, in pixels
*
- * @param offset The amount that the top and bottom values of the DisplayList are offset
* @see View#offsetTopAndBottom(int)
*/
- public abstract void offsetTopBottom(int offset);
+ public abstract void offsetTopAndBottom(float offset);
/**
- * Reset native resources. This is called when cleaning up the state of DisplayLists
+ * Reset native resources. This is called when cleaning up the state of display lists
* during destruction of hardware resources, to ensure that we do not hold onto
* obsolete resources after related resources are gone.
+ *
+ * @hide
*/
public abstract void reset();
}
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index b2988ed..c2c247e 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -172,6 +172,7 @@ public class FocusFinder {
try {
// Note: This sort is stable.
mSequentialFocusComparator.setRoot(root);
+ mSequentialFocusComparator.setIsLayoutRtl(root.isLayoutRtl());
Collections.sort(focusables, mSequentialFocusComparator);
} finally {
mSequentialFocusComparator.recycle();
@@ -180,9 +181,9 @@ public class FocusFinder {
final int count = focusables.size();
switch (direction) {
case View.FOCUS_FORWARD:
- return getForwardFocusable(root, focused, focusables, count);
+ return getNextFocusable(focused, focusables, count);
case View.FOCUS_BACKWARD:
- return getBackwardFocusable(root, focused, focusables, count);
+ return getPreviousFocusable(focused, focusables, count);
}
return focusables.get(count - 1);
}
@@ -239,13 +240,6 @@ public class FocusFinder {
return closest;
}
- private static View getForwardFocusable(ViewGroup root, View focused,
- ArrayList<View> focusables, int count) {
- return (root.isLayoutRtl()) ?
- getPreviousFocusable(focused, focusables, count) :
- getNextFocusable(focused, focusables, count);
- }
-
private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) {
if (focused != null) {
int position = focusables.lastIndexOf(focused);
@@ -259,13 +253,6 @@ public class FocusFinder {
return null;
}
- private static View getBackwardFocusable(ViewGroup root, View focused,
- ArrayList<View> focusables, int count) {
- return (root.isLayoutRtl()) ?
- getNextFocusable(focused, focusables, count) :
- getPreviousFocusable(focused, focusables, count);
- }
-
private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) {
if (focused != null) {
int position = focusables.indexOf(focused);
@@ -619,6 +606,7 @@ public class FocusFinder {
private final Rect mFirstRect = new Rect();
private final Rect mSecondRect = new Rect();
private ViewGroup mRoot;
+ private boolean mIsLayoutRtl;
public void recycle() {
mRoot = null;
@@ -628,6 +616,10 @@ public class FocusFinder {
mRoot = root;
}
+ public void setIsLayoutRtl(boolean b) {
+ mIsLayoutRtl = b;
+ }
+
public int compare(View first, View second) {
if (first == second) {
return 0;
@@ -641,17 +633,17 @@ public class FocusFinder {
} else if (mFirstRect.top > mSecondRect.top) {
return 1;
} else if (mFirstRect.left < mSecondRect.left) {
- return -1;
+ return mIsLayoutRtl ? 1 : -1;
} else if (mFirstRect.left > mSecondRect.left) {
- return 1;
+ return mIsLayoutRtl ? -1 : 1;
} else if (mFirstRect.bottom < mSecondRect.bottom) {
return -1;
} else if (mFirstRect.bottom > mSecondRect.bottom) {
return 1;
} else if (mFirstRect.right < mSecondRect.right) {
- return -1;
+ return mIsLayoutRtl ? 1 : -1;
} else if (mFirstRect.right > mSecondRect.right) {
- return 1;
+ return mIsLayoutRtl ? -1 : 1;
} else {
// The view are distinct but completely coincident so we consider
// them equal for our purposes. Since the sort is stable, this
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index b64a06e..2ec9a7d 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -144,6 +144,14 @@ class GLES20Canvas extends HardwareCanvas {
}
}
+ @Override
+ public void setName(String name) {
+ super.setName(name);
+ nSetName(mRenderer, name);
+ }
+
+ private static native void nSetName(int renderer, String name);
+
///////////////////////////////////////////////////////////////////////////
// Hardware layers
///////////////////////////////////////////////////////////////////////////
@@ -369,24 +377,13 @@ class GLES20Canvas extends HardwareCanvas {
}
private static native int nGetDisplayList(int renderer, int displayList);
-
- static void destroyDisplayList(int displayList) {
- nDestroyDisplayList(displayList);
- }
- private static native void nDestroyDisplayList(int displayList);
-
- static int getDisplayListSize(int displayList) {
- return nGetDisplayListSize(displayList);
- }
-
- private static native int nGetDisplayListSize(int displayList);
-
- static void setDisplayListName(int displayList, String name) {
- nSetDisplayListName(displayList, name);
+ @Override
+ void outputDisplayList(DisplayList displayList) {
+ nOutputDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList());
}
- private static native void nSetDisplayListName(int displayList, String name);
+ private static native void nOutputDisplayList(int renderer, int displayList);
@Override
public int drawDisplayList(DisplayList displayList, Rect dirty, int flags) {
@@ -397,24 +394,18 @@ class GLES20Canvas extends HardwareCanvas {
private static native int nDrawDisplayList(int renderer, int displayList,
Rect dirty, int flags);
- @Override
- void outputDisplayList(DisplayList displayList) {
- nOutputDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList());
- }
-
- private static native void nOutputDisplayList(int renderer, int displayList);
-
///////////////////////////////////////////////////////////////////////////
// Hardware layer
///////////////////////////////////////////////////////////////////////////
void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) {
+ layer.setLayerPaint(paint);
+
final GLES20Layer glLayer = (GLES20Layer) layer;
- final int nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawLayer(mRenderer, glLayer.getLayer(), x, y, nativePaint);
+ nDrawLayer(mRenderer, glLayer.getLayer(), x, y);
}
- private static native void nDrawLayer(int renderer, int layer, float x, float y, int paint);
+ private static native void nDrawLayer(int renderer, int layer, float x, float y);
void interrupt() {
nInterrupt(mRenderer);
@@ -433,20 +424,16 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public boolean clipPath(Path path) {
- // TODO: Implement
- path.computeBounds(mPathBounds, true);
- return nClipRect(mRenderer, mPathBounds.left, mPathBounds.top,
- mPathBounds.right, mPathBounds.bottom, Region.Op.INTERSECT.nativeInt);
+ return nClipPath(mRenderer, path.mNativePath, Region.Op.INTERSECT.nativeInt);
}
@Override
public boolean clipPath(Path path, Region.Op op) {
- // TODO: Implement
- path.computeBounds(mPathBounds, true);
- return nClipRect(mRenderer, mPathBounds.left, mPathBounds.top,
- mPathBounds.right, mPathBounds.bottom, op.nativeInt);
+ return nClipPath(mRenderer, path.mNativePath, op.nativeInt);
}
+ private static native boolean nClipPath(int renderer, int path, int op);
+
@Override
public boolean clipRect(float left, float top, float right, float bottom) {
return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
@@ -465,8 +452,8 @@ class GLES20Canvas extends HardwareCanvas {
return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
}
- private static native boolean nClipRect(int renderer, int left, int top, int right, int bottom,
- int op);
+ private static native boolean nClipRect(int renderer, int left, int top,
+ int right, int bottom, int op);
@Override
public boolean clipRect(Rect rect) {
@@ -492,20 +479,16 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public boolean clipRegion(Region region) {
- // TODO: Implement
- region.getBounds(mClipBounds);
- return nClipRect(mRenderer, mClipBounds.left, mClipBounds.top,
- mClipBounds.right, mClipBounds.bottom, Region.Op.INTERSECT.nativeInt);
+ return nClipRegion(mRenderer, region.mNativeRegion, Region.Op.INTERSECT.nativeInt);
}
@Override
public boolean clipRegion(Region region, Region.Op op) {
- // TODO: Implement
- region.getBounds(mClipBounds);
- return nClipRect(mRenderer, mClipBounds.left, mClipBounds.top,
- mClipBounds.right, mClipBounds.bottom, op.nativeInt);
+ return nClipRegion(mRenderer, region.mNativeRegion, op.nativeInt);
}
+ private static native boolean nClipRegion(int renderer, int region, int op);
+
@Override
public boolean getClipBounds(Rect bounds) {
return nGetClipBounds(mRenderer, bounds);
@@ -515,22 +498,22 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public boolean quickReject(float left, float top, float right, float bottom, EdgeType type) {
- return nQuickReject(mRenderer, left, top, right, bottom, type.nativeInt);
+ return nQuickReject(mRenderer, left, top, right, bottom);
}
private static native boolean nQuickReject(int renderer, float left, float top,
- float right, float bottom, int edge);
+ float right, float bottom);
@Override
public boolean quickReject(Path path, EdgeType type) {
path.computeBounds(mPathBounds, true);
return nQuickReject(mRenderer, mPathBounds.left, mPathBounds.top,
- mPathBounds.right, mPathBounds.bottom, type.nativeInt);
+ mPathBounds.right, mPathBounds.bottom);
}
@Override
public boolean quickReject(RectF rect, EdgeType type) {
- return nQuickReject(mRenderer, rect.left, rect.top, rect.right, rect.bottom, type.nativeInt);
+ return nQuickReject(mRenderer, rect.left, rect.top, rect.right, rect.bottom);
}
///////////////////////////////////////////////////////////////////////////
@@ -901,9 +884,9 @@ class GLES20Canvas extends HardwareCanvas {
final int count = (meshWidth + 1) * (meshHeight + 1);
checkRange(verts.length, vertOffset, count * 2);
- // TODO: Colors are ignored for now
- colors = null;
- colorOffset = 0;
+ if (colors != null) {
+ checkRange(colors.length, colorOffset, count);
+ }
int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
try {
@@ -955,6 +938,8 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawLines(float[] pts, int offset, int count, Paint paint) {
+ if (count < 4) return;
+
if ((offset | count) < 0 || offset + count > pts.length) {
throw new IllegalArgumentException("The lines array must contain 4 elements per line.");
}
@@ -1013,6 +998,17 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nDrawPath(int renderer, int path, int paint);
private static native void nDrawRects(int renderer, int region, int paint);
+ void drawRects(float[] rects, int count, Paint paint) {
+ int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
+ try {
+ nDrawRects(mRenderer, rects, count, paint.mNativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ }
+ }
+
+ private static native void nDrawRects(int renderer, float[] rects, int count, int paint);
+
@Override
public void drawPicture(Picture picture) {
if (picture.createdFromStream) {
@@ -1067,6 +1063,8 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawPoints(float[] pts, int offset, int count, Paint paint) {
+ if (count < 2) return;
+
int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
try {
nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint);
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
index e9bd0c4..3272504 100644
--- a/core/java/android/view/GLES20DisplayList.java
+++ b/core/java/android/view/GLES20DisplayList.java
@@ -58,7 +58,7 @@ class GLES20DisplayList extends DisplayList {
}
@Override
- public HardwareCanvas start() {
+ public HardwareCanvas start(int width, int height) {
if (mCanvas != null) {
throw new IllegalStateException("Recording has already started");
}
@@ -66,24 +66,25 @@ class GLES20DisplayList extends DisplayList {
mValid = false;
mCanvas = GLES20RecordingCanvas.obtain(this);
mCanvas.start();
+
+ mCanvas.setViewport(width, height);
+ // The dirty rect should always be null for a display list
+ mCanvas.onPreDraw(null);
+
return mCanvas;
}
-
@Override
- public void invalidate() {
+ public void clear() {
+ clearDirty();
+
if (mCanvas != null) {
mCanvas.recycle();
mCanvas = null;
}
mValid = false;
- }
- @Override
- public void clear() {
- if (!mValid) {
- mBitmaps.clear();
- mChildDisplayLists.clear();
- }
+ mBitmaps.clear();
+ mChildDisplayLists.clear();
}
@Override
@@ -101,11 +102,12 @@ class GLES20DisplayList extends DisplayList {
@Override
public void end() {
if (mCanvas != null) {
+ mCanvas.onPostDraw();
if (mFinalizer != null) {
mCanvas.end(mFinalizer.mNativeDisplayList);
} else {
mFinalizer = new DisplayListFinalizer(mCanvas.end(0));
- GLES20Canvas.setDisplayListName(mFinalizer.mNativeDisplayList, mName);
+ nSetDisplayListName(mFinalizer.mNativeDisplayList, mName);
}
mCanvas.recycle();
mCanvas = null;
@@ -116,9 +118,13 @@ class GLES20DisplayList extends DisplayList {
@Override
public int getSize() {
if (mFinalizer == null) return 0;
- return GLES20Canvas.getDisplayListSize(mFinalizer.mNativeDisplayList);
+ return nGetDisplayListSize(mFinalizer.mNativeDisplayList);
}
+ private static native void nDestroyDisplayList(int displayList);
+ private static native int nGetDisplayListSize(int displayList);
+ private static native void nSetDisplayListName(int displayList, String name);
+
///////////////////////////////////////////////////////////////////////////
// Native View Properties
///////////////////////////////////////////////////////////////////////////
@@ -131,20 +137,28 @@ class GLES20DisplayList extends DisplayList {
}
@Override
- public void setClipChildren(boolean clipChildren) {
+ public void setClipToBounds(boolean clipToBounds) {
if (hasNativeDisplayList()) {
- nSetClipChildren(mFinalizer.mNativeDisplayList, clipChildren);
+ nSetClipToBounds(mFinalizer.mNativeDisplayList, clipToBounds);
}
}
@Override
- public void setStaticMatrix(Matrix matrix) {
+ public void setMatrix(Matrix matrix) {
if (hasNativeDisplayList()) {
nSetStaticMatrix(mFinalizer.mNativeDisplayList, matrix.native_instance);
}
}
@Override
+ public Matrix getMatrix(Matrix matrix) {
+ if (hasNativeDisplayList()) {
+ nGetMatrix(mFinalizer.mNativeDisplayList, matrix.native_instance);
+ }
+ return matrix;
+ }
+
+ @Override
public void setAnimationMatrix(Matrix matrix) {
if (hasNativeDisplayList()) {
nSetAnimationMatrix(mFinalizer.mNativeDisplayList,
@@ -160,6 +174,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getAlpha() {
+ if (hasNativeDisplayList()) {
+ return nGetAlpha(mFinalizer.mNativeDisplayList);
+ }
+ return 1.0f;
+ }
+
+ @Override
public void setHasOverlappingRendering(boolean hasOverlappingRendering) {
if (hasNativeDisplayList()) {
nSetHasOverlappingRendering(mFinalizer.mNativeDisplayList, hasOverlappingRendering);
@@ -167,6 +189,15 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public boolean hasOverlappingRendering() {
+ //noinspection SimplifiableIfStatement
+ if (hasNativeDisplayList()) {
+ return nHasOverlappingRendering(mFinalizer.mNativeDisplayList);
+ }
+ return true;
+ }
+
+ @Override
public void setTranslationX(float translationX) {
if (hasNativeDisplayList()) {
nSetTranslationX(mFinalizer.mNativeDisplayList, translationX);
@@ -174,6 +205,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getTranslationX() {
+ if (hasNativeDisplayList()) {
+ return nGetTranslationX(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setTranslationY(float translationY) {
if (hasNativeDisplayList()) {
nSetTranslationY(mFinalizer.mNativeDisplayList, translationY);
@@ -181,6 +220,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getTranslationY() {
+ if (hasNativeDisplayList()) {
+ return nGetTranslationY(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setRotation(float rotation) {
if (hasNativeDisplayList()) {
nSetRotation(mFinalizer.mNativeDisplayList, rotation);
@@ -188,6 +235,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getRotation() {
+ if (hasNativeDisplayList()) {
+ return nGetRotation(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setRotationX(float rotationX) {
if (hasNativeDisplayList()) {
nSetRotationX(mFinalizer.mNativeDisplayList, rotationX);
@@ -195,6 +250,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getRotationX() {
+ if (hasNativeDisplayList()) {
+ return nGetRotationX(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setRotationY(float rotationY) {
if (hasNativeDisplayList()) {
nSetRotationY(mFinalizer.mNativeDisplayList, rotationY);
@@ -202,6 +265,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getRotationY() {
+ if (hasNativeDisplayList()) {
+ return nGetRotationY(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setScaleX(float scaleX) {
if (hasNativeDisplayList()) {
nSetScaleX(mFinalizer.mNativeDisplayList, scaleX);
@@ -209,6 +280,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getScaleX() {
+ if (hasNativeDisplayList()) {
+ return nGetScaleX(mFinalizer.mNativeDisplayList);
+ }
+ return 1.0f;
+ }
+
+ @Override
public void setScaleY(float scaleY) {
if (hasNativeDisplayList()) {
nSetScaleY(mFinalizer.mNativeDisplayList, scaleY);
@@ -216,6 +295,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getScaleY() {
+ if (hasNativeDisplayList()) {
+ return nGetScaleY(mFinalizer.mNativeDisplayList);
+ }
+ return 1.0f;
+ }
+
+ @Override
public void setTransformationInfo(float alpha, float translationX, float translationY,
float rotation, float rotationX, float rotationY, float scaleX, float scaleY) {
if (hasNativeDisplayList()) {
@@ -232,6 +319,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getPivotX() {
+ if (hasNativeDisplayList()) {
+ return nGetPivotX(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setPivotY(float pivotY) {
if (hasNativeDisplayList()) {
nSetPivotY(mFinalizer.mNativeDisplayList, pivotY);
@@ -239,6 +334,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getPivotY() {
+ if (hasNativeDisplayList()) {
+ return nGetPivotY(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setCameraDistance(float distance) {
if (hasNativeDisplayList()) {
nSetCameraDistance(mFinalizer.mNativeDisplayList, distance);
@@ -246,6 +349,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getCameraDistance() {
+ if (hasNativeDisplayList()) {
+ return nGetCameraDistance(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setLeft(int left) {
if (hasNativeDisplayList()) {
nSetLeft(mFinalizer.mNativeDisplayList, left);
@@ -253,6 +364,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getLeft() {
+ if (hasNativeDisplayList()) {
+ return nGetLeft(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setTop(int top) {
if (hasNativeDisplayList()) {
nSetTop(mFinalizer.mNativeDisplayList, top);
@@ -260,6 +379,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getTop() {
+ if (hasNativeDisplayList()) {
+ return nGetTop(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setRight(int right) {
if (hasNativeDisplayList()) {
nSetRight(mFinalizer.mNativeDisplayList, right);
@@ -267,6 +394,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getRight() {
+ if (hasNativeDisplayList()) {
+ return nGetRight(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setBottom(int bottom) {
if (hasNativeDisplayList()) {
nSetBottom(mFinalizer.mNativeDisplayList, bottom);
@@ -274,10 +409,11 @@ class GLES20DisplayList extends DisplayList {
}
@Override
- public void setLeftTop(int left, int top) {
+ public float getBottom() {
if (hasNativeDisplayList()) {
- nSetLeftTop(mFinalizer.mNativeDisplayList, left, top);
+ return nGetBottom(mFinalizer.mNativeDisplayList);
}
+ return 0.0f;
}
@Override
@@ -288,25 +424,24 @@ class GLES20DisplayList extends DisplayList {
}
@Override
- public void offsetLeftRight(int offset) {
+ public void offsetLeftAndRight(float offset) {
if (hasNativeDisplayList()) {
- nOffsetLeftRight(mFinalizer.mNativeDisplayList, offset);
+ nOffsetLeftAndRight(mFinalizer.mNativeDisplayList, offset);
}
}
@Override
- public void offsetTopBottom(int offset) {
+ public void offsetTopAndBottom(float offset) {
if (hasNativeDisplayList()) {
- nOffsetTopBottom(mFinalizer.mNativeDisplayList, offset);
+ nOffsetTopAndBottom(mFinalizer.mNativeDisplayList, offset);
}
}
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 nOffsetTopAndBottom(int displayList, float offset);
+ private static native void nOffsetLeftAndRight(int displayList, float offset);
private static native void nSetLeftTopRightBottom(int displayList, int left, int top,
int right, int bottom);
- private static native void nSetLeftTop(int displayList, int left, int top);
private static native void nSetBottom(int displayList, int bottom);
private static native void nSetRight(int displayList, int right);
private static native void nSetTop(int displayList, int top);
@@ -315,7 +450,7 @@ class GLES20DisplayList extends DisplayList {
private static native void nSetPivotY(int displayList, float pivotY);
private static native void nSetPivotX(int displayList, float pivotX);
private static native void nSetCaching(int displayList, boolean caching);
- private static native void nSetClipChildren(int displayList, boolean clipChildren);
+ private static native void nSetClipToBounds(int displayList, boolean clipToBounds);
private static native void nSetAlpha(int displayList, float alpha);
private static native void nSetHasOverlappingRendering(int displayList,
boolean hasOverlappingRendering);
@@ -332,6 +467,23 @@ class GLES20DisplayList extends DisplayList {
private static native void nSetStaticMatrix(int displayList, int nativeMatrix);
private static native void nSetAnimationMatrix(int displayList, int animationMatrix);
+ private static native boolean nHasOverlappingRendering(int displayList);
+ private static native void nGetMatrix(int displayList, int matrix);
+ private static native float nGetAlpha(int displayList);
+ private static native float nGetLeft(int displayList);
+ private static native float nGetTop(int displayList);
+ private static native float nGetRight(int displayList);
+ private static native float nGetBottom(int displayList);
+ private static native float nGetCameraDistance(int displayList);
+ private static native float nGetScaleX(int displayList);
+ private static native float nGetScaleY(int displayList);
+ private static native float nGetTranslationX(int displayList);
+ private static native float nGetTranslationY(int displayList);
+ private static native float nGetRotation(int displayList);
+ private static native float nGetRotationX(int displayList);
+ private static native float nGetRotationY(int displayList);
+ private static native float nGetPivotX(int displayList);
+ private static native float nGetPivotY(int displayList);
///////////////////////////////////////////////////////////////////////////
// Finalization
@@ -347,7 +499,7 @@ class GLES20DisplayList extends DisplayList {
@Override
protected void finalize() throws Throwable {
try {
- GLES20Canvas.destroyDisplayList(mNativeDisplayList);
+ nDestroyDisplayList(mNativeDisplayList);
} finally {
super.finalize();
}
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
index 812fb97..7ee628b 100644
--- a/core/java/android/view/GLES20Layer.java
+++ b/core/java/android/view/GLES20Layer.java
@@ -53,12 +53,12 @@ abstract class GLES20Layer extends HardwareLayer {
}
@Override
- boolean copyInto(Bitmap bitmap) {
+ public boolean copyInto(Bitmap bitmap) {
return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap);
}
@Override
- void destroy() {
+ public void destroy() {
if (mFinalizer != null) {
mFinalizer.destroy();
mFinalizer = null;
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index d2df45a..7da2451 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -24,10 +24,7 @@ import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
-import android.util.Pool;
-import android.util.Poolable;
-import android.util.PoolableManager;
-import android.util.Pools;
+import android.util.Pools.SynchronizedPool;
/**
* An implementation of a GL canvas that records drawing operations.
@@ -35,35 +32,25 @@ import android.util.Pools;
* Bitmap objects that it draws, preventing the backing memory of Bitmaps from being freed while
* the DisplayList is still holding a native reference to the memory.
*/
-class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20RecordingCanvas> {
+class GLES20RecordingCanvas extends GLES20Canvas {
// The recording canvas pool should be large enough to handle a deeply nested
// view hierarchy because display lists are generated recursively.
private static final int POOL_LIMIT = 25;
- private static final Pool<GLES20RecordingCanvas> sPool = Pools.synchronizedPool(
- Pools.finitePool(new PoolableManager<GLES20RecordingCanvas>() {
- public GLES20RecordingCanvas newInstance() {
- return new GLES20RecordingCanvas();
- }
- @Override
- public void onAcquired(GLES20RecordingCanvas element) {
- }
- @Override
- public void onReleased(GLES20RecordingCanvas element) {
- }
- }, POOL_LIMIT));
-
- private GLES20RecordingCanvas mNextPoolable;
- private boolean mIsPooled;
+ private static final SynchronizedPool<GLES20RecordingCanvas> sPool =
+ new SynchronizedPool<GLES20RecordingCanvas>(POOL_LIMIT);
private GLES20DisplayList mDisplayList;
private GLES20RecordingCanvas() {
- super(true /*record*/, true /*translucent*/);
+ super(true, true);
}
static GLES20RecordingCanvas obtain(GLES20DisplayList displayList) {
GLES20RecordingCanvas canvas = sPool.acquire();
+ if (canvas == null) {
+ canvas = new GLES20RecordingCanvas();
+ }
canvas.mDisplayList = displayList;
return canvas;
}
@@ -300,24 +287,4 @@ class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20Recor
colorOffset, indices, indexOffset, indexCount, paint);
recordShaderBitmap(paint);
}
-
- @Override
- public GLES20RecordingCanvas getNextPoolable() {
- return mNextPoolable;
- }
-
- @Override
- public void setNextPoolable(GLES20RecordingCanvas element) {
- mNextPoolable = element;
- }
-
- @Override
- public boolean isPooled() {
- return mIsPooled;
- }
-
- @Override
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
}
diff --git a/core/java/android/view/GLES20RenderLayer.java b/core/java/android/view/GLES20RenderLayer.java
index 44d4719..685dc70 100644
--- a/core/java/android/view/GLES20RenderLayer.java
+++ b/core/java/android/view/GLES20RenderLayer.java
@@ -89,6 +89,10 @@ class GLES20RenderLayer extends GLES20Layer {
@Override
void end(Canvas currentCanvas) {
+ HardwareCanvas canvas = getCanvas();
+ if (canvas != null) {
+ canvas.onPostDraw();
+ }
if (currentCanvas instanceof GLES20Canvas) {
((GLES20Canvas) currentCanvas).resume();
}
@@ -99,7 +103,10 @@ class GLES20RenderLayer extends GLES20Layer {
if (currentCanvas instanceof GLES20Canvas) {
((GLES20Canvas) currentCanvas).interrupt();
}
- return getCanvas();
+ HardwareCanvas canvas = getCanvas();
+ canvas.setViewport(mWidth, mHeight);
+ canvas.onPreDraw(null);
+ return canvas;
}
/**
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 9ddb32e..28c1058 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -213,6 +213,7 @@ public class GestureDetector {
private OnDoubleTapListener mDoubleTapListener;
private boolean mStillDown;
+ private boolean mDeferConfirmSingleTap;
private boolean mInLongPress;
private boolean mAlwaysInTapRegion;
private boolean mAlwaysInBiggerTapRegion;
@@ -267,8 +268,12 @@ public class GestureDetector {
case TAP:
// If the user's finger is still down, do not count it as a tap
- if (mDoubleTapListener != null && !mStillDown) {
- mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+ if (mDoubleTapListener != null) {
+ if (!mStillDown) {
+ mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+ } else {
+ mDeferConfirmSingleTap = true;
+ }
}
break;
@@ -533,6 +538,7 @@ public class GestureDetector {
mAlwaysInBiggerTapRegion = true;
mStillDown = true;
mInLongPress = false;
+ mDeferConfirmSingleTap = false;
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
@@ -586,6 +592,9 @@ public class GestureDetector {
mInLongPress = false;
} else if (mAlwaysInTapRegion) {
handled = mListener.onSingleTapUp(ev);
+ if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
+ mDoubleTapListener.onSingleTapConfirmed(ev);
+ }
} else {
// A fling must travel the minimum tap distance
@@ -612,6 +621,7 @@ public class GestureDetector {
mVelocityTracker = null;
}
mIsDoubleTapping = false;
+ mDeferConfirmSingleTap = false;
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
@@ -637,6 +647,7 @@ public class GestureDetector {
mStillDown = false;
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
+ mDeferConfirmSingleTap = false;
if (mInLongPress) {
mInLongPress = false;
}
@@ -649,6 +660,7 @@ public class GestureDetector {
mIsDoubleTapping = false;
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
+ mDeferConfirmSingleTap = false;
if (mInLongPress) {
mInLongPress = false;
}
@@ -671,6 +683,7 @@ public class GestureDetector {
private void dispatchLongPress() {
mHandler.removeMessages(TAP);
+ mDeferConfirmSingleTap = false;
mInLongPress = true;
mListener.onLongPress(mCurrentDownEvent);
}
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index eeae3ed..0dfed69 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -23,10 +23,12 @@ import android.graphics.Rect;
/**
* Hardware accelerated canvas.
- *
- * @hide
+ *
+ * @hide
*/
public abstract class HardwareCanvas extends Canvas {
+ private String mName;
+
@Override
public boolean isHardwareAccelerated() {
return true;
@@ -36,33 +38,76 @@ public abstract class HardwareCanvas extends Canvas {
public void setBitmap(Bitmap bitmap) {
throw new UnsupportedOperationException();
}
-
+
+ /**
+ * Specifies the name of this canvas. Naming the canvas is entirely
+ * optional but can be useful for debugging purposes.
+ *
+ * @param name The name of the canvas, can be null
+ *
+ * @see #getName()
+ *
+ * @hide
+ */
+ public void setName(String name) {
+ mName = name;
+ }
+
+ /**
+ * Returns the name of this canvas.
+ *
+ * @return The name of the canvas or null
+ *
+ * @see #setName(String)
+ *
+ * @hide
+ */
+ public String getName() {
+ return mName;
+ }
+
/**
* Invoked before any drawing operation is performed in this canvas.
*
* @param dirty The dirty rectangle to update, can be null.
* @return {@link DisplayList#STATUS_DREW} if anything was drawn (such as a call to clear
- * the canvas).
+ * the canvas).
+ *
+ * @hide
*/
public abstract int onPreDraw(Rect dirty);
/**
* Invoked after all drawing operation have been performed.
+ *
+ * @hide
*/
public abstract void onPostDraw();
/**
+ * Draws the specified display list onto this canvas. The display list can only
+ * be drawn if {@link android.view.DisplayList#isValid()} returns true.
+ *
+ * @param displayList The display list to replay.
+ */
+ public void drawDisplayList(DisplayList displayList) {
+ drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN);
+ }
+
+ /**
* Draws the specified display list onto this canvas.
*
* @param displayList The display list to replay.
* @param dirty The dirty region to redraw in the next pass, matters only
- * if this method returns true, can be null.
+ * if this method returns {@link DisplayList#STATUS_DRAW}, can be null.
* @param flags Optional flags about drawing, see {@link DisplayList} for
* the possible flags.
*
* @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW}, or
* {@link DisplayList#STATUS_INVOKE}, or'd with {@link DisplayList#STATUS_DREW}
* if anything was drawn.
+ *
+ * @hide
*/
public abstract int drawDisplayList(DisplayList displayList, Rect dirty, int flags);
@@ -71,6 +116,8 @@ public abstract class HardwareCanvas extends Canvas {
* tools to output display lists for selected nodes to the log.
*
* @param displayList The display list to be logged.
+ *
+ * @hide
*/
abstract void outputDisplayList(DisplayList displayList);
@@ -81,6 +128,8 @@ public abstract class HardwareCanvas extends Canvas {
* @param x The left coordinate of the layer
* @param y The top coordinate of the layer
* @param paint The paint used to draw the layer
+ *
+ * @hide
*/
abstract void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint);
@@ -93,6 +142,8 @@ public abstract class HardwareCanvas extends Canvas {
*
* @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or
* {@link DisplayList#STATUS_INVOKE}
+ *
+ * @hide
*/
public int callDrawGLFunction(int drawGLFunction) {
// Noop - this is done in the display list recorder subclass
@@ -106,6 +157,8 @@ public abstract class HardwareCanvas extends Canvas {
*
* @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or
* {@link DisplayList#STATUS_INVOKE}
+ *
+ * @hide
*/
public int invokeFunctors(Rect dirty) {
return DisplayList.STATUS_DONE;
@@ -118,7 +171,9 @@ public abstract class HardwareCanvas extends Canvas {
*
* @see #invokeFunctors(android.graphics.Rect)
* @see #callDrawGLFunction(int)
- * @see #detachFunctor(int)
+ * @see #detachFunctor(int)
+ *
+ * @hide
*/
abstract void detachFunctor(int functor);
@@ -129,7 +184,9 @@ public abstract class HardwareCanvas extends Canvas {
*
* @see #invokeFunctors(android.graphics.Rect)
* @see #callDrawGLFunction(int)
- * @see #detachFunctor(int)
+ * @see #detachFunctor(int)
+ *
+ * @hide
*/
abstract void attachFunctor(int functor);
@@ -139,13 +196,17 @@ public abstract class HardwareCanvas extends Canvas {
* @param layer The layer to update
*
* @see #clearLayerUpdates()
+ *
+ * @hide
*/
abstract void pushLayerUpdate(HardwareLayer layer);
/**
* Removes all enqueued layer updates.
*
- * @see #pushLayerUpdate(HardwareLayer)
+ * @see #pushLayerUpdate(HardwareLayer)
+ *
+ * @hide
*/
abstract void clearLayerUpdates();
}
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index d3bc35a..18b838b 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -24,7 +24,7 @@ import android.graphics.Rect;
/**
* A hardware layer can be used to render graphics operations into a hardware
- * friendly buffer. For instance, with an OpenGL backend, a hardware layer
+ * friendly buffer. For instance, with an OpenGL backend a hardware layer
* would use a Frame Buffer Object (FBO.) The hardware layer can be used as
* a drawing cache when a complex set of graphics operations needs to be
* drawn several times.
@@ -68,7 +68,7 @@ abstract class HardwareLayer {
* @param paint The paint used when the layer is drawn into the destination canvas.
* @see View#setLayerPaint(android.graphics.Paint)
*/
- void setLayerPaint(Paint paint) {}
+ void setLayerPaint(Paint paint) { }
/**
* Returns the minimum width of the layer.
@@ -144,6 +144,9 @@ abstract class HardwareLayer {
* this layer.
*
* @return A hardware canvas, or null if a canvas cannot be created
+ *
+ * @see #start(android.graphics.Canvas)
+ * @see #end(android.graphics.Canvas)
*/
abstract HardwareCanvas getCanvas();
@@ -154,12 +157,14 @@ abstract class HardwareLayer {
/**
* This must be invoked before drawing onto this layer.
+ *
* @param currentCanvas
*/
abstract HardwareCanvas start(Canvas currentCanvas);
-
+
/**
* This must be invoked after drawing onto this layer.
+ *
* @param currentCanvas
*/
abstract void end(Canvas currentCanvas);
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 5b7a5af..8308459 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
package android.view;
import android.content.ComponentCallbacks2;
@@ -29,6 +28,7 @@ import android.os.Looper;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
+import android.util.DisplayMetrics;
import android.util.Log;
import com.google.android.gles_jni.EGLImpl;
@@ -42,13 +42,14 @@ import javax.microedition.khronos.opengles.GL;
import java.io.File;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
import static javax.microedition.khronos.egl.EGL10.*;
/**
- * Interface for rendering a ViewAncestor using hardware acceleration.
- *
+ * Interface for rendering a view hierarchy using hardware acceleration.
+ *
* @hide
*/
public abstract class HardwareRenderer {
@@ -62,9 +63,9 @@ public abstract class HardwareRenderer {
/**
* Turn on to only refresh the parts of the screen that need updating.
* When turned on the property defined by {@link #RENDER_DIRTY_REGIONS_PROPERTY}
- * must also have the value "true".
+ * must also have the value "true".
*/
- public static final boolean RENDER_DIRTY_REGIONS = true;
+ static final boolean RENDER_DIRTY_REGIONS = true;
/**
* System property used to enable or disable dirty regions invalidation.
@@ -76,16 +77,6 @@ public abstract class HardwareRenderer {
* "false", to disable partial invalidates
*/
static final String RENDER_DIRTY_REGIONS_PROPERTY = "debug.hwui.render_dirty_regions";
-
- /**
- * System property used to enable or disable vsync.
- * The default value of this property is assumed to be false.
- *
- * Possible values:
- * "true", to disable vsync
- * "false", to enable vsync
- */
- static final String DISABLE_VSYNC_PROPERTY = "debug.hwui.disable_vsync";
/**
* System property used to enable or disable hardware rendering profiling.
@@ -97,13 +88,34 @@ public abstract class HardwareRenderer {
*
* Possible values:
* "true", to enable profiling
+ * "visual_bars", to enable profiling and visualize the results on screen
+ * "visual_lines", to enable profiling and visualize the results on screen
* "false", to disable profiling
- *
+ *
+ * @see #PROFILE_PROPERTY_VISUALIZE_BARS
+ * @see #PROFILE_PROPERTY_VISUALIZE_LINES
+ *
* @hide
*/
public static final String PROFILE_PROPERTY = "debug.hwui.profile";
/**
+ * Value for {@link #PROFILE_PROPERTY}. When the property is set to this
+ * value, profiling data will be visualized on screen as a bar chart.
+ *
+ * @hide
+ */
+ public static final String PROFILE_PROPERTY_VISUALIZE_BARS = "visual_bars";
+
+ /**
+ * Value for {@link #PROFILE_PROPERTY}. When the property is set to this
+ * value, profiling data will be visualized on screen as a line chart.
+ *
+ * @hide
+ */
+ public static final String PROFILE_PROPERTY_VISUALIZE_LINES = "visual_lines";
+
+ /**
* System property used to specify the number of frames to be used
* when doing hardware rendering profiling.
* The default value of this property is #PROFILE_MAX_FRAMES.
@@ -161,6 +173,19 @@ public abstract class HardwareRenderer {
public static final String DEBUG_SHOW_OVERDRAW_PROPERTY = "debug.hwui.show_overdraw";
/**
+ * Turn on to debug non-rectangular clip operations.
+ *
+ * Possible values:
+ * "hide", to disable this debug mode
+ * "highlight", highlight drawing commands tested against a non-rectangular clip
+ * "stencil", renders the clip region on screen when set
+ *
+ * @hide
+ */
+ public static final String DEBUG_SHOW_NON_RECTANGULAR_CLIP_PROPERTY =
+ "debug.hwui.show_non_rect_clip";
+
+ /**
* A process can set this flag to false to prevent the use of hardware
* rendering.
*
@@ -323,10 +348,26 @@ public abstract class HardwareRenderer {
abstract long getFrameCount();
/**
+ * Loads system properties used by the renderer. This method is invoked
+ * whenever system properties are modified. Implementations can use this
+ * to trigger live updates of the renderer based on properties.
+ *
+ * @param surface The surface to update with the new properties.
+ * Can be null.
+ *
+ * @return True if a property has changed.
+ */
+ abstract boolean loadSystemProperties(Surface surface);
+
+ private static native boolean nLoadProperties();
+
+ /**
* Sets the directory to use as a persistent storage for hardware rendering
* resources.
*
* @param cacheDir A directory the current process can write to
+ *
+ * @hide
*/
public static void setupDiskCache(File cacheDir) {
nSetupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
@@ -338,7 +379,7 @@ public abstract class HardwareRenderer {
* Notifies EGL that the frame is about to be rendered.
* @param size
*/
- private static void beginFrame(int[] size) {
+ static void beginFrame(int[] size) {
nBeginFrame(size);
}
@@ -373,15 +414,6 @@ public abstract class HardwareRenderer {
private static native boolean nIsBackBufferPreserved();
/**
- * Disables v-sync. For performance testing only.
- */
- static void disableVsync() {
- nDisableVsync();
- }
-
- private static native void nDisableVsync();
-
- /**
* Indicates that the specified hardware layer needs to be updated
* as soon as possible.
*
@@ -396,6 +428,8 @@ public abstract class HardwareRenderer {
interface HardwareDrawCallbacks {
/**
* Invoked before a view is drawn by a hardware renderer.
+ * This method can be used to apply transformations to the
+ * canvas but no drawing command should be issued.
*
* @param canvas The Canvas used to render the view.
*/
@@ -403,6 +437,7 @@ public abstract class HardwareRenderer {
/**
* Invoked after a view is drawn by a hardware renderer.
+ * It is safe to invoke drawing commands from this method.
*
* @param canvas The Canvas used to render the view.
*/
@@ -416,20 +451,19 @@ public abstract class HardwareRenderer {
* @param attachInfo AttachInfo tied to the specified view.
* @param callbacks Callbacks invoked when drawing happens.
* @param dirty The dirty rectangle to update, can be null.
- *
- * @return true if the dirty rect was ignored, false otherwise
*/
- abstract boolean draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
+ abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
Rect dirty);
/**
* Creates a new display list that can be used to record batches of
* drawing operations.
*
- * @param name The name of the display list, used for debugging purpose.
- * May be null
+ * @param name The name of the display list, used for debugging purpose. May be null.
*
* @return A new display list.
+ *
+ * @hide
*/
public abstract DisplayList createDisplayList(String name);
@@ -457,7 +491,6 @@ public abstract class HardwareRenderer {
/**
* Creates a new {@link SurfaceTexture} that can be used to render into the
* specified hardware layer.
- *
*
* @param layer The layer to render into using a {@link android.graphics.SurfaceTexture}
*
@@ -526,6 +559,13 @@ public abstract class HardwareRenderer {
}
/**
+ * Optional, sets the name of the renderer. Useful for debugging purposes.
+ *
+ * @param name The name of this renderer, can be null
+ */
+ abstract void setName(String name);
+
+ /**
* Creates a hardware renderer using OpenGL.
*
* @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.)
@@ -612,6 +652,100 @@ public abstract class HardwareRenderer {
mRequested = requested;
}
+ /**
+ * Describes a series of frames that should be drawn on screen as a graph.
+ * Each frame is composed of 1 or more elements.
+ */
+ abstract class GraphDataProvider {
+ /**
+ * Draws the graph as bars. Frame elements are stacked on top of
+ * each other.
+ */
+ public static final int GRAPH_TYPE_BARS = 0;
+ /**
+ * Draws the graph as lines. The number of series drawn corresponds
+ * to the number of elements.
+ */
+ public static final int GRAPH_TYPE_LINES = 1;
+
+ /**
+ * Returns the type of graph to render.
+ *
+ * @return {@link #GRAPH_TYPE_BARS} or {@link #GRAPH_TYPE_LINES}
+ */
+ abstract int getGraphType();
+
+ /**
+ * This method is invoked before the graph is drawn. This method
+ * can be used to compute sizes, etc.
+ *
+ * @param metrics The display metrics
+ */
+ abstract void prepare(DisplayMetrics metrics);
+
+ /**
+ * @return The size in pixels of a vertical unit.
+ */
+ abstract int getVerticalUnitSize();
+
+ /**
+ * @return The size in pixels of a horizontal unit.
+ */
+ abstract int getHorizontalUnitSize();
+
+ /**
+ * @return The size in pixels of the margin between horizontal units.
+ */
+ abstract int getHorizontaUnitMargin();
+
+ /**
+ * An optional threshold value.
+ *
+ * @return A value >= 0 to draw the threshold, a negative value
+ * to ignore it.
+ */
+ abstract float getThreshold();
+
+ /**
+ * The data to draw in the graph. The number of elements in the
+ * array must be at least {@link #getFrameCount()} * {@link #getElementCount()}.
+ * If a value is negative the following values will be ignored.
+ */
+ abstract float[] getData();
+
+ /**
+ * Returns the number of frames to render in the graph.
+ */
+ abstract int getFrameCount();
+
+ /**
+ * Returns the number of elements in each frame. This directly affects
+ * the number of series drawn in the graph.
+ */
+ abstract int getElementCount();
+
+ /**
+ * Returns the current frame, if any. If the returned value is negative
+ * the current frame is ignored.
+ */
+ abstract int getCurrentFrame();
+
+ /**
+ * Prepares the paint to draw the specified element (or series.)
+ */
+ abstract void setupGraphPaint(Paint paint, int elementIndex);
+
+ /**
+ * Prepares the paint to draw the threshold.
+ */
+ abstract void setupThresholdPaint(Paint paint);
+
+ /**
+ * Prepares the paint to draw the current frame indicator.
+ */
+ abstract void setupCurrentFramePaint(Paint paint);
+ }
+
@SuppressWarnings({"deprecation"})
static abstract class GlRenderer extends HardwareRenderer {
static final int SURFACE_STATE_ERROR = 0;
@@ -620,6 +754,14 @@ public abstract class HardwareRenderer {
static final int FUNCTOR_PROCESS_DELAY = 4;
+ private static final int PROFILE_DRAW_MARGIN = 0;
+ private static final int PROFILE_DRAW_WIDTH = 3;
+ private static final int[] PROFILE_DRAW_COLORS = { 0xcf3e66cc, 0xcfdc3912, 0xcfe69800 };
+ private static final int PROFILE_DRAW_CURRENT_FRAME_COLOR = 0xcf5faa4d;
+ private static final int PROFILE_DRAW_THRESHOLD_COLOR = 0xff5faa4d;
+ private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2;
+ private static final int PROFILE_DRAW_DP_PER_MS = 7;
+
static EGL10 sEgl;
static EGLDisplay sEglDisplay;
static EGLConfig sEglConfig;
@@ -637,6 +779,8 @@ public abstract class HardwareRenderer {
GL mGl;
HardwareCanvas mCanvas;
+ String mName;
+
long mFrameCount;
Paint mDebugPaint;
@@ -652,15 +796,18 @@ public abstract class HardwareRenderer {
boolean mDirtyRegionsEnabled;
boolean mUpdateDirtyRegions;
- final boolean mVsyncDisabled;
-
- final boolean mProfileEnabled;
- final float[] mProfileData;
- final ReentrantLock mProfileLock;
+ boolean mProfileEnabled;
+ int mProfileVisualizerType = -1;
+ float[] mProfileData;
+ ReentrantLock mProfileLock;
int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
-
- final boolean mDebugDirtyRegions;
- final boolean mShowOverdraw;
+
+ GraphDataProvider mDebugDataProvider;
+ float[][] mProfileShapes;
+ Paint mProfilePaint;
+
+ boolean mDebugDirtyRegions;
+ boolean mShowOverdraw;
final int mGlVersion;
final boolean mTranslucent;
@@ -675,44 +822,90 @@ public abstract class HardwareRenderer {
GlRenderer(int glVersion, boolean translucent) {
mGlVersion = glVersion;
mTranslucent = translucent;
-
- String property;
- property = SystemProperties.get(DISABLE_VSYNC_PROPERTY, "false");
- mVsyncDisabled = "true".equalsIgnoreCase(property);
- if (mVsyncDisabled) {
- Log.d(LOG_TAG, "Disabling v-sync");
+ loadSystemProperties(null);
+ }
+
+ private static final String[] VISUALIZERS = {
+ PROFILE_PROPERTY_VISUALIZE_BARS,
+ PROFILE_PROPERTY_VISUALIZE_LINES
+ };
+
+ @Override
+ boolean loadSystemProperties(Surface surface) {
+ boolean value;
+ boolean changed = false;
+
+ String profiling = SystemProperties.get(PROFILE_PROPERTY);
+ int graphType = Arrays.binarySearch(VISUALIZERS, profiling);
+ value = graphType >= 0;
+
+ if (graphType != mProfileVisualizerType) {
+ changed = true;
+ mProfileVisualizerType = graphType;
+
+ mProfileShapes = null;
+ mProfilePaint = null;
+
+ if (value) {
+ mDebugDataProvider = new DrawPerformanceDataProvider(graphType);
+ } else {
+ mDebugDataProvider = null;
+ }
}
- property = SystemProperties.get(PROFILE_PROPERTY, "false");
- mProfileEnabled = "true".equalsIgnoreCase(property);
- if (mProfileEnabled) {
- Log.d(LOG_TAG, "Profiling hardware renderer");
+ // If on-screen profiling is not enabled, we need to check whether
+ // console profiling only is enabled
+ if (!value) {
+ value = Boolean.parseBoolean(profiling);
}
- if (mProfileEnabled) {
- property = SystemProperties.get(PROFILE_MAXFRAMES_PROPERTY,
- Integer.toString(PROFILE_MAX_FRAMES));
- int maxProfileFrames = Integer.valueOf(property);
- mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT];
- for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
- mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
+ if (value != mProfileEnabled) {
+ changed = true;
+ mProfileEnabled = value;
+
+ if (mProfileEnabled) {
+ Log.d(LOG_TAG, "Profiling hardware renderer");
+
+ int maxProfileFrames = SystemProperties.getInt(PROFILE_MAXFRAMES_PROPERTY,
+ PROFILE_MAX_FRAMES);
+ mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT];
+ for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
+ mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
+ }
+
+ mProfileLock = new ReentrantLock();
+ } else {
+ mProfileData = null;
+ mProfileLock = null;
+ mProfileVisualizerType = -1;
}
- mProfileLock = new ReentrantLock();
- } else {
- mProfileData = null;
- mProfileLock = null;
+ mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
}
- property = SystemProperties.get(DEBUG_DIRTY_REGIONS_PROPERTY, "false");
- mDebugDirtyRegions = "true".equalsIgnoreCase(property);
- if (mDebugDirtyRegions) {
- Log.d(LOG_TAG, "Debugging dirty regions");
+ value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false);
+ if (value != mDebugDirtyRegions) {
+ changed = true;
+ mDebugDirtyRegions = value;
+
+ if (mDebugDirtyRegions) {
+ Log.d(LOG_TAG, "Debugging dirty regions");
+ }
}
- mShowOverdraw = SystemProperties.getBoolean(
+ value = SystemProperties.getBoolean(
HardwareRenderer.DEBUG_SHOW_OVERDRAW_PROPERTY, false);
+ if (value != mShowOverdraw) {
+ changed = true;
+ mShowOverdraw = value;
+ }
+
+ if (nLoadProperties()) {
+ changed = true;
+ }
+
+ return changed;
}
@Override
@@ -795,12 +988,9 @@ public abstract class HardwareRenderer {
} else {
if (mCanvas == null) {
mCanvas = createCanvas();
+ mCanvas.setName(mName);
}
- if (mCanvas != null) {
- setEnabled(true);
- } else {
- Log.w(LOG_TAG, "Hardware accelerated Canvas could not be created");
- }
+ setEnabled(true);
}
return mCanvas != null;
@@ -842,19 +1032,7 @@ public abstract class HardwareRenderer {
checkEglErrorsForced();
- sEglConfig = chooseEglConfig();
- if (sEglConfig == null) {
- // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without
- if (sDirtyRegions) {
- sDirtyRegions = false;
- sEglConfig = chooseEglConfig();
- if (sEglConfig == null) {
- throw new RuntimeException("eglConfig not initialized");
- }
- } else {
- throw new RuntimeException("eglConfig not initialized");
- }
- }
+ sEglConfig = loadEglConfig();
}
}
@@ -868,6 +1046,23 @@ public abstract class HardwareRenderer {
}
}
+ private EGLConfig loadEglConfig() {
+ EGLConfig eglConfig = chooseEglConfig();
+ if (eglConfig == null) {
+ // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without
+ if (sDirtyRegions) {
+ sDirtyRegions = false;
+ eglConfig = chooseEglConfig();
+ if (eglConfig == null) {
+ throw new RuntimeException("eglConfig not initialized");
+ }
+ } else {
+ throw new RuntimeException("eglConfig not initialized");
+ }
+ }
+ return eglConfig;
+ }
+
abstract ManagedEGLContext createManagedContext(EGLContext eglContext);
private EGLConfig chooseEglConfig() {
@@ -1104,6 +1299,11 @@ public abstract class HardwareRenderer {
return mCanvas;
}
+ @Override
+ void setName(String name) {
+ mName = name;
+ }
+
boolean canDraw() {
return mGl != null && mCanvas != null;
}
@@ -1134,7 +1334,7 @@ public abstract class HardwareRenderer {
}
@Override
- boolean draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
+ void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
Rect dirty) {
if (canDraw()) {
if (!hasDirtyRegions()) {
@@ -1154,93 +1354,27 @@ public abstract class HardwareRenderer {
mProfileLock.lock();
}
- // We had to change the current surface and/or context, redraw everything
- if (surfaceState == SURFACE_STATE_UPDATED) {
- dirty = null;
- beginFrame(null);
- } else {
- int[] size = mSurfaceSize;
- beginFrame(size);
-
- if (size[1] != mHeight || size[0] != mWidth) {
- mWidth = size[0];
- mHeight = size[1];
-
- canvas.setViewport(mWidth, mHeight);
+ dirty = beginFrame(canvas, dirty, surfaceState);
- dirty = null;
- }
- }
+ DisplayList displayList = buildDisplayList(view, canvas);
int saveCount = 0;
int status = DisplayList.STATUS_DONE;
try {
- view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
- == View.PFLAG_INVALIDATED;
- view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
-
- long getDisplayListStartTime = 0;
- if (mProfileEnabled) {
- mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT;
- if (mProfileCurrentFrame >= mProfileData.length) {
- mProfileCurrentFrame = 0;
- }
-
- getDisplayListStartTime = System.nanoTime();
- }
-
- canvas.clearLayerUpdates();
+ status = prepareFrame(dirty);
- DisplayList displayList;
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
- try {
- displayList = view.getDisplayList();
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
-
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame");
- try {
- status = onPreDraw(dirty);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
saveCount = canvas.save();
callbacks.onHardwarePreDraw(canvas);
- if (mProfileEnabled) {
- long now = System.nanoTime();
- float total = (now - getDisplayListStartTime) * 0.000001f;
- //noinspection PointlessArithmeticExpression
- mProfileData[mProfileCurrentFrame] = total;
- }
-
if (displayList != null) {
- long drawDisplayListStartTime = 0;
- if (mProfileEnabled) {
- drawDisplayListStartTime = System.nanoTime();
- }
-
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList");
- try {
- status |= canvas.drawDisplayList(displayList, mRedrawClip,
- DisplayList.FLAG_CLIP_CHILDREN);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
-
- if (mProfileEnabled) {
- long now = System.nanoTime();
- float total = (now - drawDisplayListStartTime) * 0.000001f;
- mProfileData[mProfileCurrentFrame + 1] = total;
- }
-
- handleFunctorStatus(attachInfo, status);
+ status |= drawDisplayList(attachInfo, canvas, displayList, status);
} else {
// Shouldn't reach here
view.draw(canvas);
}
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "An error has occurred while drawing:", e);
} finally {
callbacks.onHardwarePostDraw(canvas);
canvas.restoreToCount(saveCount);
@@ -1248,48 +1382,154 @@ public abstract class HardwareRenderer {
mFrameCount++;
- if (mDebugDirtyRegions) {
- if (mDebugPaint == null) {
- mDebugPaint = new Paint();
- mDebugPaint.setColor(0x7fff0000);
- }
-
- if (dirty != null && (mFrameCount & 1) == 0) {
- canvas.drawRect(dirty, mDebugPaint);
- }
- }
+ debugDirtyRegions(dirty, canvas);
+ drawProfileData(attachInfo);
}
onPostDraw();
- attachInfo.mIgnoreDirtyState = false;
-
- if ((status & DisplayList.STATUS_DREW) == DisplayList.STATUS_DREW) {
- long eglSwapBuffersStartTime = 0;
- if (mProfileEnabled) {
- eglSwapBuffersStartTime = System.nanoTime();
- }
-
- sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
-
- if (mProfileEnabled) {
- long now = System.nanoTime();
- float total = (now - eglSwapBuffersStartTime) * 0.000001f;
- mProfileData[mProfileCurrentFrame + 2] = total;
- }
-
- checkEglErrors();
- }
+ swapBuffers(status);
if (mProfileEnabled) {
mProfileLock.unlock();
}
- return dirty == null;
+ attachInfo.mIgnoreDirtyState = false;
}
}
+ }
- return false;
+ private DisplayList buildDisplayList(View view, HardwareCanvas canvas) {
+ view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
+ == View.PFLAG_INVALIDATED;
+ view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
+
+ long buildDisplayListStartTime = startBuildDisplayListProfiling();
+ canvas.clearLayerUpdates();
+
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
+ DisplayList displayList = view.getDisplayList();
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+
+ endBuildDisplayListProfiling(buildDisplayListStartTime);
+
+ return displayList;
+ }
+
+ abstract void drawProfileData(View.AttachInfo attachInfo);
+
+ private Rect beginFrame(HardwareCanvas canvas, Rect dirty, int surfaceState) {
+ // We had to change the current surface and/or context, redraw everything
+ if (surfaceState == SURFACE_STATE_UPDATED) {
+ dirty = null;
+ beginFrame(null);
+ } else {
+ int[] size = mSurfaceSize;
+ beginFrame(size);
+
+ if (size[1] != mHeight || size[0] != mWidth) {
+ mWidth = size[0];
+ mHeight = size[1];
+
+ canvas.setViewport(mWidth, mHeight);
+
+ dirty = null;
+ }
+ }
+
+ if (mDebugDataProvider != null) dirty = null;
+
+ return dirty;
+ }
+
+ private long startBuildDisplayListProfiling() {
+ if (mProfileEnabled) {
+ mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT;
+ if (mProfileCurrentFrame >= mProfileData.length) {
+ mProfileCurrentFrame = 0;
+ }
+
+ return System.nanoTime();
+ }
+ return 0;
+ }
+
+ private void endBuildDisplayListProfiling(long getDisplayListStartTime) {
+ if (mProfileEnabled) {
+ long now = System.nanoTime();
+ float total = (now - getDisplayListStartTime) * 0.000001f;
+ //noinspection PointlessArithmeticExpression
+ mProfileData[mProfileCurrentFrame] = total;
+ }
+ }
+
+ private int prepareFrame(Rect dirty) {
+ int status;
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame");
+ try {
+ status = onPreDraw(dirty);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ return status;
+ }
+
+ private int drawDisplayList(View.AttachInfo attachInfo, HardwareCanvas canvas,
+ DisplayList displayList, int status) {
+
+ long drawDisplayListStartTime = 0;
+ if (mProfileEnabled) {
+ drawDisplayListStartTime = System.nanoTime();
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList");
+ try {
+ status |= canvas.drawDisplayList(displayList, mRedrawClip,
+ DisplayList.FLAG_CLIP_CHILDREN);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+
+ if (mProfileEnabled) {
+ long now = System.nanoTime();
+ float total = (now - drawDisplayListStartTime) * 0.000001f;
+ mProfileData[mProfileCurrentFrame + 1] = total;
+ }
+
+ handleFunctorStatus(attachInfo, status);
+ return status;
+ }
+
+ private void swapBuffers(int status) {
+ if ((status & DisplayList.STATUS_DREW) == DisplayList.STATUS_DREW) {
+ long eglSwapBuffersStartTime = 0;
+ if (mProfileEnabled) {
+ eglSwapBuffersStartTime = System.nanoTime();
+ }
+
+ sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
+
+ if (mProfileEnabled) {
+ long now = System.nanoTime();
+ float total = (now - eglSwapBuffersStartTime) * 0.000001f;
+ mProfileData[mProfileCurrentFrame + 2] = total;
+ }
+
+ checkEglErrors();
+ }
+ }
+
+ private void debugDirtyRegions(Rect dirty, HardwareCanvas canvas) {
+ if (mDebugDirtyRegions) {
+ if (mDebugPaint == null) {
+ mDebugPaint = new Paint();
+ mDebugPaint.setColor(0x7fff0000);
+ }
+
+ if (dirty != null && (mFrameCount & 1) == 0) {
+ canvas.drawRect(dirty, mDebugPaint);
+ }
+ }
}
private void handleFunctorStatus(View.AttachInfo attachInfo, int status) {
@@ -1362,6 +1602,96 @@ public abstract class HardwareRenderer {
}
return SURFACE_STATE_SUCCESS;
}
+
+ private static int dpToPx(int dp, float density) {
+ return (int) (dp * density + 0.5f);
+ }
+
+ class DrawPerformanceDataProvider extends GraphDataProvider {
+ private final int mGraphType;
+
+ private int mVerticalUnit;
+ private int mHorizontalUnit;
+ private int mHorizontalMargin;
+ private int mThresholdStroke;
+
+ DrawPerformanceDataProvider(int graphType) {
+ mGraphType = graphType;
+ }
+
+ @Override
+ void prepare(DisplayMetrics metrics) {
+ final float density = metrics.density;
+
+ mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density);
+ mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density);
+ mHorizontalMargin = dpToPx(PROFILE_DRAW_MARGIN, density);
+ mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
+ }
+
+ @Override
+ int getGraphType() {
+ return mGraphType;
+ }
+
+ @Override
+ int getVerticalUnitSize() {
+ return mVerticalUnit;
+ }
+
+ @Override
+ int getHorizontalUnitSize() {
+ return mHorizontalUnit;
+ }
+
+ @Override
+ int getHorizontaUnitMargin() {
+ return mHorizontalMargin;
+ }
+
+ @Override
+ float[] getData() {
+ return mProfileData;
+ }
+
+ @Override
+ float getThreshold() {
+ return 16;
+ }
+
+ @Override
+ int getFrameCount() {
+ return mProfileData.length / PROFILE_FRAME_DATA_COUNT;
+ }
+
+ @Override
+ int getElementCount() {
+ return PROFILE_FRAME_DATA_COUNT;
+ }
+
+ @Override
+ int getCurrentFrame() {
+ return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT;
+ }
+
+ @Override
+ void setupGraphPaint(Paint paint, int elementIndex) {
+ paint.setColor(PROFILE_DRAW_COLORS[elementIndex]);
+ if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
+ }
+
+ @Override
+ void setupThresholdPaint(Paint paint) {
+ paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR);
+ paint.setStrokeWidth(mThresholdStroke);
+ }
+
+ @Override
+ void setupCurrentFramePaint(Paint paint) {
+ paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR);
+ if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
+ }
+ }
}
/**
@@ -1370,6 +1700,8 @@ public abstract class HardwareRenderer {
static class Gl20Renderer extends GlRenderer {
private GLES20Canvas mGlCanvas;
+ private DisplayMetrics mDisplayMetrics;
+
private static EGLSurface sPbuffer;
private static final Object[] sPbufferLock = new Object[0];
@@ -1436,6 +1768,10 @@ public abstract class HardwareRenderer {
@Override
int[] getConfig(boolean dirtyRegions) {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ final int stencilSize = GLES20Canvas.getStencilSize();
+ final int swapBehavior = dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+
return new int[] {
EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
@@ -1444,14 +1780,12 @@ public abstract class HardwareRenderer {
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_CONFIG_CAVEAT, EGL_NONE,
- // TODO: Find a better way to choose the stencil size
- EGL_STENCIL_SIZE, mShowOverdraw ? GLES20Canvas.getStencilSize() : 0,
- EGL_SURFACE_TYPE, EGL_WINDOW_BIT |
- (dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
+ EGL_STENCIL_SIZE, stencilSize,
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
EGL_NONE
};
}
-
+
@Override
void initCaches() {
GLES20Canvas.initCaches();
@@ -1473,6 +1807,153 @@ public abstract class HardwareRenderer {
}
@Override
+ void drawProfileData(View.AttachInfo attachInfo) {
+ if (mDebugDataProvider != null) {
+ final GraphDataProvider provider = mDebugDataProvider;
+ initProfileDrawData(attachInfo, provider);
+
+ final int height = provider.getVerticalUnitSize();
+ final int margin = provider.getHorizontaUnitMargin();
+ final int width = provider.getHorizontalUnitSize();
+
+ int x = 0;
+ int count = 0;
+ int current = 0;
+
+ final float[] data = provider.getData();
+ final int elementCount = provider.getElementCount();
+ final int graphType = provider.getGraphType();
+
+ int totalCount = provider.getFrameCount() * elementCount;
+ if (graphType == GraphDataProvider.GRAPH_TYPE_LINES) {
+ totalCount -= elementCount;
+ }
+
+ for (int i = 0; i < totalCount; i += elementCount) {
+ if (data[i] < 0.0f) break;
+
+ int index = count * 4;
+ if (i == provider.getCurrentFrame() * elementCount) current = index;
+
+ x += margin;
+ int x2 = x + width;
+
+ int y2 = mHeight;
+ int y1 = (int) (y2 - data[i] * height);
+
+ switch (graphType) {
+ case GraphDataProvider.GRAPH_TYPE_BARS: {
+ for (int j = 0; j < elementCount; j++) {
+ //noinspection MismatchedReadAndWriteOfArray
+ final float[] r = mProfileShapes[j];
+ r[index] = x;
+ r[index + 1] = y1;
+ r[index + 2] = x2;
+ r[index + 3] = y2;
+
+ y2 = y1;
+ if (j < elementCount - 1) {
+ y1 = (int) (y2 - data[i + j + 1] * height);
+ }
+ }
+ } break;
+ case GraphDataProvider.GRAPH_TYPE_LINES: {
+ for (int j = 0; j < elementCount; j++) {
+ //noinspection MismatchedReadAndWriteOfArray
+ final float[] r = mProfileShapes[j];
+ r[index] = (x + x2) * 0.5f;
+ r[index + 1] = index == 0 ? y1 : r[index - 1];
+ r[index + 2] = r[index] + width;
+ r[index + 3] = y1;
+
+ y2 = y1;
+ if (j < elementCount - 1) {
+ y1 = (int) (y2 - data[i + j + 1] * height);
+ }
+ }
+ } break;
+ }
+
+
+ x += width;
+ count++;
+ }
+
+ x += margin;
+
+ drawGraph(graphType, count);
+ drawCurrentFrame(graphType, current);
+ drawThreshold(x, height);
+ }
+ }
+
+ private void drawGraph(int graphType, int count) {
+ for (int i = 0; i < mProfileShapes.length; i++) {
+ mDebugDataProvider.setupGraphPaint(mProfilePaint, i);
+ switch (graphType) {
+ case GraphDataProvider.GRAPH_TYPE_BARS:
+ mGlCanvas.drawRects(mProfileShapes[i], count * 4, mProfilePaint);
+ break;
+ case GraphDataProvider.GRAPH_TYPE_LINES:
+ mGlCanvas.drawLines(mProfileShapes[i], 0, count * 4, mProfilePaint);
+ break;
+ }
+ }
+ }
+
+ private void drawCurrentFrame(int graphType, int index) {
+ if (index >= 0) {
+ mDebugDataProvider.setupCurrentFramePaint(mProfilePaint);
+ switch (graphType) {
+ case GraphDataProvider.GRAPH_TYPE_BARS:
+ mGlCanvas.drawRect(mProfileShapes[2][index], mProfileShapes[2][index + 1],
+ mProfileShapes[2][index + 2], mProfileShapes[0][index + 3],
+ mProfilePaint);
+ break;
+ case GraphDataProvider.GRAPH_TYPE_LINES:
+ mGlCanvas.drawLine(mProfileShapes[2][index], mProfileShapes[2][index + 1],
+ mProfileShapes[2][index], mHeight, mProfilePaint);
+ break;
+ }
+ }
+ }
+
+ private void drawThreshold(int x, int height) {
+ float threshold = mDebugDataProvider.getThreshold();
+ if (threshold > 0.0f) {
+ mDebugDataProvider.setupThresholdPaint(mProfilePaint);
+ int y = (int) (mHeight - threshold * height);
+ mGlCanvas.drawLine(0.0f, y, x, y, mProfilePaint);
+ }
+ }
+
+ private void initProfileDrawData(View.AttachInfo attachInfo, GraphDataProvider provider) {
+ if (mProfileShapes == null) {
+ final int elementCount = provider.getElementCount();
+ final int frameCount = provider.getFrameCount();
+
+ mProfileShapes = new float[elementCount][];
+ for (int i = 0; i < elementCount; i++) {
+ mProfileShapes[i] = new float[frameCount * 4];
+ }
+
+ mProfilePaint = new Paint();
+ }
+
+ mProfilePaint.reset();
+ if (provider.getGraphType() == GraphDataProvider.GRAPH_TYPE_LINES) {
+ mProfilePaint.setAntiAlias(true);
+ }
+
+ if (mDisplayMetrics == null) {
+ mDisplayMetrics = new DisplayMetrics();
+ }
+
+ attachInfo.mDisplay.getMetrics(mDisplayMetrics);
+ provider.prepare(mDisplayMetrics);
+ }
+
+ @Override
void destroy(boolean full) {
try {
super.destroy(full);
@@ -1484,14 +1965,6 @@ public abstract class HardwareRenderer {
}
@Override
- void setup(int width, int height) {
- super.setup(width, height);
- if (mVsyncDisabled) {
- disableVsync();
- }
- }
-
- @Override
void pushLayerUpdate(HardwareLayer layer) {
mGlCanvas.pushLayerUpdate(layer);
}
@@ -1507,12 +1980,12 @@ public abstract class HardwareRenderer {
}
@Override
- HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
+ public HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
return new GLES20RenderLayer(width, height, isOpaque);
}
@Override
- SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
+ public SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
return ((GLES20TextureLayer) layer).getSurfaceTexture();
}
diff --git a/core/java/android/view/IDisplayContentChangeListener.aidl b/core/java/android/view/IMagnificationCallbacks.aidl
index ef7edea..032d073 100644
--- a/core/java/android/view/IDisplayContentChangeListener.aidl
+++ b/core/java/android/view/IMagnificationCallbacks.aidl
@@ -1,7 +1,7 @@
/*
** Copyright 2012, The Android Open Source Project
**
-** Licensed under the Apache License, Version 2.0 (the "License")
+** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
@@ -16,18 +16,14 @@
package android.view;
-import android.os.IBinder;
-import android.view.WindowInfo;
-import android.graphics.Rect;
+import android.graphics.Region;
/**
- * 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 onWindowLayersChanged(int displayId);
+oneway interface IMagnificationCallbacks {
+ void onMagnifedBoundsChanged(in Region bounds);
+ void onRectangleOnScreenRequested(int left, int top, int right, int bottom);
void onRotationChanged(int rotation);
+ void onUserContextChanged();
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 15bd46c..8ec07ef 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -45,7 +45,7 @@ oneway interface IWindow {
*/
void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor);
- void resized(in Rect frame, in Rect contentInsets,
+ void resized(in Rect frame, in Rect overscanInsets, in Rect contentInsets,
in Rect visibleInsets, boolean reportDraw, in Configuration newConfig);
void moved(int newX, int newY);
void dispatchAppVisibility(boolean visible);
diff --git a/core/java/android/view/IWindowFocusObserver.aidl b/core/java/android/view/IWindowFocusObserver.aidl
new file mode 100644
index 0000000..d14bb48
--- /dev/null
+++ b/core/java/android/view/IWindowFocusObserver.aidl
@@ -0,0 +1,23 @@
+/* Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+/** {@hide} */
+interface IWindowFocusObserver
+{
+ void focusGained(IBinder inputToken);
+ void focusLost(IBinder inputToken);
+}
diff --git a/core/java/android/view/IWindowId.aidl b/core/java/android/view/IWindowId.aidl
new file mode 100644
index 0000000..ac8f2db
--- /dev/null
+++ b/core/java/android/view/IWindowId.aidl
@@ -0,0 +1,26 @@
+/* Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+import android.view.IWindowFocusObserver;
+
+/** {@hide} */
+interface IWindowId
+{
+ void registerFocusObserver(IWindowFocusObserver observer);
+ void unregisterFocusObserver(IWindowFocusObserver observer);
+ boolean isFocused();
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 2b6cbcf..8ed4a86 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -27,17 +27,17 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.view.IApplicationToken;
-import android.view.IDisplayContentChangeListener;
+import android.view.IMagnificationCallbacks;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
import android.view.IWindowSession;
import android.view.KeyEvent;
import android.view.InputEvent;
+import android.view.MagnificationSpec;
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.
@@ -60,11 +60,17 @@ interface IWindowManager
in IInputContext inputContext);
boolean inputMethodClientHasFocus(IInputMethodClient client);
+ void getInitialDisplaySize(int displayId, out Point size);
+ void getBaseDisplaySize(int displayId, out Point size);
void setForcedDisplaySize(int displayId, int width, int height);
void clearForcedDisplaySize(int displayId);
+ int getInitialDisplayDensity(int displayId);
+ int getBaseDisplayDensity(int displayId);
void setForcedDisplayDensity(int displayId, int density);
void clearForcedDisplayDensity(int displayId);
+ void setOverscan(int displayId, int left, int top, int right, int bottom);
+
// Is the device configured to have a full system bar for larger screens?
boolean hasSystemNavBar();
@@ -74,7 +80,7 @@ interface IWindowManager
void setEventDispatching(boolean enabled);
void addWindowToken(IBinder token, int type);
void removeWindowToken(IBinder token);
- void addAppToken(int addPos, int userId, IApplicationToken token,
+ void addAppToken(int addPos, IApplicationToken token,
int groupId, int requestedOrientation, boolean fullscreen, boolean showWhenLocked);
void setAppGroupId(IBinder token, int groupId);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
@@ -169,6 +175,12 @@ interface IWindowManager
int watchRotation(IRotationWatcher watcher);
/**
+ * Remove a rotation watcher set using watchRotation.
+ * @hide
+ */
+ void removeRotationWatcher(IRotationWatcher watcher);
+
+ /**
* Determine the preferred edge of the screen to pin the compact options menu against.
* @return a Gravity value for the options menu panel
* @hide
@@ -190,6 +202,13 @@ interface IWindowManager
void thawRotation();
/**
+ * Gets whether the rotation is frozen.
+ *
+ * @return Whether the rotation is frozen.
+ */
+ boolean isRotationFrozen();
+
+ /**
* Create a screenshot of the applications currently displayed.
*/
Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight);
@@ -221,48 +240,49 @@ interface IWindowManager
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.
+ * Gets the frame of a window given its token.
*/
- void addDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener);
+ void getWindowFrame(IBinder token, out Rect outFrame);
/**
- * Removes a listener for display content changes.
+ * Device is in safe mode.
*/
- void removeDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener);
+ boolean isSafeModeEnabled();
/**
- * Gets the info for a window given its token.
+ * Tell keyguard to show the assistant (Intent.ACTION_ASSIST) after asking for the user's
+ * credentials.
*/
- WindowInfo getWindowInfo(IBinder token);
+ void showAssistant();
/**
- * Gets the infos for all visible windows.
+ * Sets the display magnification callbacks. These callbacks notify
+ * the client for contextual changes related to display magnification.
+ *
+ * @param callbacks The magnification callbacks.
*/
- void getVisibleWindowsForDisplay(int displayId, out List<WindowInfo> outInfos);
+ void setMagnificationCallbacks(IMagnificationCallbacks callbacks);
/**
- * Device is in safe mode.
+ * Sets the magnification spec to be applied to all windows that can be
+ * magnified.
+ *
+ * @param spec The current magnification spec.
*/
- boolean isSafeModeEnabled();
+ void setMagnificationSpec(in MagnificationSpec spec);
/**
- * Tell keyguard to show the assistant (Intent.ACTION_ASSIST) after asking for the user's
- * credentials.
+ * Gets the magnification spec for a window given its token. If the
+ * window has a compatibility scale it is also folded in the returned
+ * magnification spec.
+ *
+ * @param windowToken The unique window token.
+ * @return The magnification spec if such or null.
*/
- void showAssistant();
+ MagnificationSpec getCompatibleMagnificationSpecForWindow(in IBinder windowToken);
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index ff9dcce..c32a2c9 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -24,6 +24,7 @@ import android.graphics.Region;
import android.os.Bundle;
import android.view.InputChannel;
import android.view.IWindow;
+import android.view.IWindowId;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.Surface;
@@ -63,6 +64,9 @@ interface IWindowSession {
* {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}.
* @param outFrame Rect in which is placed the new position/size on
* screen.
+ * @param outOverscanInsets Rect in which is placed the offsets from
+ * <var>outFrame</var> in which the content of the window are inside
+ * of the display's overlay region.
* @param outContentInsets Rect in which is placed the offsets from
* <var>outFrame</var> in which the content of the window should be
* placed. This can be used to modify the window layout to ensure its
@@ -84,7 +88,7 @@ interface IWindowSession {
*/
int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
- int flags, out Rect outFrame,
+ int flags, out Rect outFrame, out Rect outOverscanInsets,
out Rect outContentInsets, out Rect outVisibleInsets,
out Configuration outConfig, out Surface outSurface);
@@ -185,4 +189,6 @@ interface IWindowSession {
* Notifies that a rectangle on the screen has been requested.
*/
void onRectangleOnScreenRequested(IBinder token, in Rect rectangle, boolean immediate);
+
+ IWindowId getWindowId(IBinder window);
}
diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java
index 523af04..40ee1ad 100644
--- a/core/java/android/view/InputChannel.java
+++ b/core/java/android/view/InputChannel.java
@@ -54,6 +54,7 @@ public final class InputChannel implements Parcelable {
private native void nativeTransferTo(InputChannel other);
private native void nativeReadFromParcel(Parcel parcel);
private native void nativeWriteToParcel(Parcel parcel);
+ private native void nativeDup(InputChannel target);
private native String nativeGetName();
@@ -64,7 +65,7 @@ public final class InputChannel implements Parcelable {
*/
public InputChannel() {
}
-
+
@Override
protected void finalize() throws Throwable {
try {
@@ -78,7 +79,9 @@ public final class InputChannel implements Parcelable {
* Creates a new input channel pair. One channel should be provided to the input
* dispatcher and the other to the application's input queue.
* @param name The descriptive (non-unique) name of the channel pair.
- * @return A pair of input channels. They are symmetric and indistinguishable.
+ * @return A pair of input channels. The first channel is designated as the
+ * server channel and should be used to publish input events. The second channel
+ * is designated as the client channel and should be used to consume input events.
*/
public static InputChannel[] openInputChannelPair(String name) {
if (name == null) {
@@ -123,10 +126,20 @@ public final class InputChannel implements Parcelable {
nativeTransferTo(outParameter);
}
+ /**
+ * Duplicates the input channel.
+ */
+ public InputChannel dup() {
+ InputChannel target = new InputChannel();
+ nativeDup(target);
+ return target;
+ }
+
+ @Override
public int describeContents() {
return Parcelable.CONTENTS_FILE_DESCRIPTOR;
}
-
+
public void readFromParcel(Parcel in) {
if (in == null) {
throw new IllegalArgumentException("in must not be null");
@@ -134,7 +147,8 @@ public final class InputChannel implements Parcelable {
nativeReadFromParcel(in);
}
-
+
+ @Override
public void writeToParcel(Parcel out, int flags) {
if (out == null) {
throw new IllegalArgumentException("out must not be null");
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 3bb9c01..2a761c1 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -62,7 +62,14 @@ public final class InputDevice implements Parcelable {
* specify the desired interpretation for its input events.
*/
public static final int SOURCE_CLASS_MASK = 0x000000ff;
-
+
+ /**
+ * The input source has no class.
+ *
+ * It is up to the application to determine how to handle the device based on the device type.
+ */
+ public static final int SOURCE_CLASS_NONE = 0x00000000;
+
/**
* The input source has buttons or keys.
* Examples: {@link #SOURCE_KEYBOARD}, {@link #SOURCE_DPAD}.
@@ -202,6 +209,17 @@ public final class InputDevice implements Parcelable {
public static final int SOURCE_TOUCHPAD = 0x00100000 | SOURCE_CLASS_POSITION;
/**
+ * The input source is a touch device whose motions should be interpreted as navigation events.
+ *
+ * For example, an upward swipe should be as an upward focus traversal in the same manner as
+ * pressing up on a D-Pad would be. Swipes to the left, right and down should be treated in a
+ * similar manner.
+ *
+ * @see #SOURCE_CLASS_NONE
+ */
+ public static final int SOURCE_TOUCH_NAVIGATION = 0x00200000 | SOURCE_CLASS_NONE;
+
+ /**
* The input source is a joystick.
* (It may also be a {@link #SOURCE_GAMEPAD}).
*
@@ -353,8 +371,8 @@ public final class InputDevice implements Parcelable {
if (axis < 0) {
break;
}
- addMotionRange(axis, in.readInt(),
- in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat());
+ addMotionRange(axis, in.readInt(), in.readFloat(), in.readFloat(), in.readFloat(),
+ in.readFloat(), in.readFloat());
}
}
@@ -515,7 +533,6 @@ public final class InputDevice implements Parcelable {
*
* @see MotionEvent#AXIS_X
* @see MotionEvent#AXIS_Y
- * @see #getSupportedAxes()
*/
public MotionRange getMotionRange(int axis) {
final int numRanges = mMotionRanges.size();
@@ -541,7 +558,6 @@ public final class InputDevice implements Parcelable {
*
* @see MotionEvent#AXIS_X
* @see MotionEvent#AXIS_Y
- * @see #getSupportedAxes()
*/
public MotionRange getMotionRange(int axis, int source) {
final int numRanges = mMotionRanges.size();
@@ -566,8 +582,8 @@ public final class InputDevice implements Parcelable {
// Called from native code.
private void addMotionRange(int axis, int source,
- float min, float max, float flat, float fuzz) {
- mMotionRanges.add(new MotionRange(axis, source, min, max, flat, fuzz));
+ float min, float max, float flat, float fuzz, float resolution) {
+ mMotionRanges.add(new MotionRange(axis, source, min, max, flat, fuzz, resolution));
}
/**
@@ -607,14 +623,17 @@ public final class InputDevice implements Parcelable {
private float mMax;
private float mFlat;
private float mFuzz;
+ private float mResolution;
- private MotionRange(int axis, int source, float min, float max, float flat, float fuzz) {
+ private MotionRange(int axis, int source, float min, float max, float flat, float fuzz,
+ float resolution) {
mAxis = axis;
mSource = source;
mMin = min;
mMax = max;
mFlat = flat;
mFuzz = fuzz;
+ mResolution = resolution;
}
/**
@@ -633,6 +652,19 @@ public final class InputDevice implements Parcelable {
return mSource;
}
+
+ /**
+ * Determines whether the event is from the given source.
+ *
+ * @param source The input source to check against. This can be a specific device type,
+ * such as {@link InputDevice#SOURCE_TOUCH_NAVIGATION}, or a more generic device class,
+ * such as {@link InputDevice#SOURCE_CLASS_POINTER}.
+ * @return Whether the event is from the given source.
+ */
+ public boolean isFromSource(int source) {
+ return (getSource() & source) == source;
+ }
+
/**
* Gets the inclusive minimum value for the axis.
* @return The inclusive minimum value.
@@ -680,6 +712,14 @@ public final class InputDevice implements Parcelable {
public float getFuzz() {
return mFuzz;
}
+
+ /**
+ * Gets the resolution for input device measurements with respect to this axis.
+ * @return The resolution in units per millimeter, or units per radian for rotational axes.
+ */
+ public float getResolution() {
+ return mResolution;
+ }
}
@Override
@@ -703,6 +743,7 @@ public final class InputDevice implements Parcelable {
out.writeFloat(range.mMax);
out.writeFloat(range.mFlat);
out.writeFloat(range.mFuzz);
+ out.writeFloat(range.mResolution);
}
out.writeInt(-1);
}
@@ -757,6 +798,7 @@ public final class InputDevice implements Parcelable {
description.append(" max=").append(range.mMax);
description.append(" flat=").append(range.mFlat);
description.append(" fuzz=").append(range.mFuzz);
+ description.append(" resolution=").append(range.mResolution);
description.append("\n");
}
return description.toString();
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index ef810a3..07a937c 100644
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -70,7 +70,6 @@ public abstract class InputEvent implements Parcelable {
* Gets the source of the event.
*
* @return The event source or {@link InputDevice#SOURCE_UNKNOWN} if unknown.
- * @see InputDevice#getSourceInfo
*/
public abstract int getSource();
@@ -83,6 +82,18 @@ public abstract class InputEvent implements Parcelable {
public abstract void setSource(int source);
/**
+ * Determines whether the event is from the given source.
+ *
+ * @param source The input source to check against. This can be a specific device type, such as
+ * {@link InputDevice#SOURCE_TOUCH_NAVIGATION}, or a more generic device class, such as
+ * {@link InputDevice#SOURCE_CLASS_POINTER}.
+ * @return Whether the event is from the given source.
+ */
+ public boolean isFromSource(int source) {
+ return (getSource() & source) == source;
+ }
+
+ /**
* Copies the event.
*
* @return A deep copy of the event.
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 117c101..f5ee7ed 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -23,6 +23,8 @@ import android.os.MessageQueue;
import android.util.Log;
import android.util.SparseIntArray;
+import java.lang.ref.WeakReference;
+
/**
* Provides a low-level mechanism for an application to receive input events.
* @hide
@@ -42,7 +44,7 @@ public abstract class InputEventReceiver {
// Map from InputEvent sequence numbers to dispatcher sequence numbers.
private final SparseIntArray mSeqMap = new SparseIntArray();
- private static native int nativeInit(InputEventReceiver receiver,
+ private static native int nativeInit(WeakReference<InputEventReceiver> receiver,
InputChannel inputChannel, MessageQueue messageQueue);
private static native void nativeDispose(int receiverPtr);
private static native void nativeFinishInputEvent(int receiverPtr, int seq, boolean handled);
@@ -65,7 +67,8 @@ public abstract class InputEventReceiver {
mInputChannel = inputChannel;
mMessageQueue = looper.getQueue();
- mReceiverPtr = nativeInit(this, inputChannel, mMessageQueue);
+ mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
+ inputChannel, mMessageQueue);
mCloseGuard.open("dispose");
}
diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java
new file mode 100644
index 0000000..be6a623
--- /dev/null
+++ b/core/java/android/view/InputEventSender.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import dalvik.system.CloseGuard;
+
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.util.Log;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Provides a low-level mechanism for an application to send input events.
+ * @hide
+ */
+public abstract class InputEventSender {
+ private static final String TAG = "InputEventSender";
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private int mSenderPtr;
+
+ // We keep references to the input channel and message queue objects here so that
+ // they are not GC'd while the native peer of the receiver is using them.
+ private InputChannel mInputChannel;
+ private MessageQueue mMessageQueue;
+
+ private static native int nativeInit(WeakReference<InputEventSender> sender,
+ InputChannel inputChannel, MessageQueue messageQueue);
+ private static native void nativeDispose(int senderPtr);
+ private static native boolean nativeSendKeyEvent(int senderPtr, int seq, KeyEvent event);
+ private static native boolean nativeSendMotionEvent(int senderPtr, int seq, MotionEvent event);
+
+ /**
+ * Creates an input event sender bound to the specified input channel.
+ *
+ * @param inputChannel The input channel.
+ * @param looper The looper to use when invoking callbacks.
+ */
+ public InputEventSender(InputChannel inputChannel, Looper looper) {
+ if (inputChannel == null) {
+ throw new IllegalArgumentException("inputChannel must not be null");
+ }
+ if (looper == null) {
+ throw new IllegalArgumentException("looper must not be null");
+ }
+
+ mInputChannel = inputChannel;
+ mMessageQueue = looper.getQueue();
+ mSenderPtr = nativeInit(new WeakReference<InputEventSender>(this),
+ inputChannel, mMessageQueue);
+
+ mCloseGuard.open("dispose");
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Disposes the receiver.
+ */
+ public void dispose() {
+ dispose(false);
+ }
+
+ private void dispose(boolean finalized) {
+ if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
+ }
+ mCloseGuard.close();
+ }
+
+ if (mSenderPtr != 0) {
+ nativeDispose(mSenderPtr);
+ mSenderPtr = 0;
+ }
+ mInputChannel = null;
+ mMessageQueue = null;
+ }
+
+ /**
+ * Called when an input event is finished.
+ *
+ * @param seq The input event sequence number.
+ * @param handled True if the input event was handled.
+ */
+ public void onInputEventFinished(int seq, boolean handled) {
+ }
+
+ /**
+ * Sends an input event.
+ * Must be called on the same Looper thread to which the sender is attached.
+ *
+ * @param seq The input event sequence number.
+ * @param event The input event to send.
+ * @return True if the entire event was sent successfully. May return false
+ * if the input channel buffer filled before all samples were dispatched.
+ */
+ public final boolean sendInputEvent(int seq, InputEvent event) {
+ if (event == null) {
+ throw new IllegalArgumentException("event must not be null");
+ }
+ if (mSenderPtr == 0) {
+ Log.w(TAG, "Attempted to send an input event but the input event "
+ + "sender has already been disposed.");
+ return false;
+ }
+
+ if (event instanceof KeyEvent) {
+ return nativeSendKeyEvent(mSenderPtr, seq, (KeyEvent)event);
+ } else {
+ return nativeSendMotionEvent(mSenderPtr, seq, (MotionEvent)event);
+ }
+ }
+
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchInputEventFinished(int seq, boolean handled) {
+ onInputEventFinished(seq, handled);
+ }
+}
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
index 909a3b2..e3de89d 100644
--- a/core/java/android/view/InputQueue.java
+++ b/core/java/android/view/InputQueue.java
@@ -16,11 +16,127 @@
package android.view;
+import dalvik.system.CloseGuard;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.util.Pools.Pool;
+import android.util.Pools.SimplePool;
+import android.util.SparseArray;
+
+import java.lang.ref.WeakReference;
+
/**
* An input queue provides a mechanism for an application to receive incoming
* input events. Currently only usable from native code.
*/
public final class InputQueue {
+ private final SparseArray<ActiveInputEvent> mActiveEventArray =
+ new SparseArray<ActiveInputEvent>(20);
+ private final Pool<ActiveInputEvent> mActiveInputEventPool =
+ new SimplePool<ActiveInputEvent>(20);
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private int mPtr;
+
+ private static native int nativeInit(WeakReference<InputQueue> weakQueue,
+ MessageQueue messageQueue);
+ private static native int nativeSendKeyEvent(int ptr, KeyEvent e, boolean preDispatch);
+ private static native int nativeSendMotionEvent(int ptr, MotionEvent e);
+ private static native void nativeDispose(int ptr);
+
+ /** @hide */
+ public InputQueue() {
+ mPtr = nativeInit(new WeakReference<InputQueue>(this), Looper.myQueue());
+
+ mCloseGuard.open("dispose");
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /** @hide */
+ public void dispose() {
+ dispose(false);
+ }
+
+ /** @hide */
+ public void dispose(boolean finalized) {
+ if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
+ }
+ mCloseGuard.close();
+ }
+
+ if (mPtr != 0) {
+ nativeDispose(mPtr);
+ mPtr = 0;
+ }
+ }
+
+ /** @hide */
+ public int getNativePtr() {
+ return mPtr;
+ }
+
+ /** @hide */
+ public void sendInputEvent(InputEvent e, Object token, boolean predispatch,
+ FinishedInputEventCallback callback) {
+ ActiveInputEvent event = obtainActiveInputEvent(token, callback);
+ int id;
+ if (e instanceof KeyEvent) {
+ id = nativeSendKeyEvent(mPtr, (KeyEvent) e, predispatch);
+ } else {
+ id = nativeSendMotionEvent(mPtr, (MotionEvent) e);
+ }
+ mActiveEventArray.put(id, event);
+ }
+
+ private void finishInputEvent(int id, boolean handled) {
+ int index = mActiveEventArray.indexOfKey(id);
+ if (index >= 0) {
+ ActiveInputEvent e = mActiveEventArray.valueAt(index);
+ mActiveEventArray.removeAt(index);
+ e.mCallback.onFinishedInputEvent(e.mToken, handled);
+ recycleActiveInputEvent(e);
+ }
+ }
+
+ private ActiveInputEvent obtainActiveInputEvent(Object token,
+ FinishedInputEventCallback callback) {
+ ActiveInputEvent e = mActiveInputEventPool.acquire();
+ if (e == null) {
+ e = new ActiveInputEvent();
+ }
+ e.mToken = token;
+ e.mCallback = callback;
+ return e;
+ }
+
+ private void recycleActiveInputEvent(ActiveInputEvent e) {
+ e.recycle();
+ mActiveInputEventPool.release(e);
+ }
+
+ private final class ActiveInputEvent {
+ public Object mToken;
+ public FinishedInputEventCallback mCallback;
+
+ public void recycle() {
+ mToken = null;
+ mCallback = null;
+ }
+ }
+
/**
* Interface to receive notification of when an InputQueue is associated
* and dissociated with a thread.
@@ -31,7 +147,7 @@ public final class InputQueue {
* thread making this call, so it can start receiving events from it.
*/
void onInputQueueCreated(InputQueue queue);
-
+
/**
* Called when the given InputQueue is no longer associated with
* the thread and thus not dispatching events.
@@ -39,15 +155,9 @@ public final class InputQueue {
void onInputQueueDestroyed(InputQueue queue);
}
- final InputChannel mChannel;
-
- /** @hide */
- public InputQueue(InputChannel channel) {
- mChannel = channel;
- }
-
/** @hide */
- public InputChannel getInputChannel() {
- return mChannel;
+ public static interface FinishedInputEventCallback {
+ void onFinishedInputEvent(Object token, boolean handled);
}
+
}
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 2cb724f..9e5f25a 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -180,6 +180,8 @@ public class KeyCharacterMap implements Parcelable {
private static final int ACCENT_CIRCUMFLEX_LEGACY = '^';
private static final int ACCENT_TILDE_LEGACY = '~';
+ private static final int CHAR_SPACE = ' ';
+
/**
* Maps Unicode combining diacritical to display-form dead key.
*/
@@ -473,14 +475,23 @@ public class KeyCharacterMap implements Parcelable {
}
/**
- * Get the character that is produced by putting accent on the character c.
+ * Get the character that is produced by combining the dead key producing accent
+ * with the key producing character c.
* For example, getDeadChar('`', 'e') returns &egrave;.
+ * getDeadChar('^', ' ') returns '^' and getDeadChar('^', '^') returns '^'.
*
* @param accent The accent character. eg. '`'
* @param c The basic character.
* @return The combined character, or 0 if the characters cannot be combined.
*/
public static int getDeadChar(int accent, int c) {
+ if (c == accent || CHAR_SPACE == c) {
+ // The same dead character typed twice or a dead character followed by a
+ // space should both produce the non-combining version of the combining char.
+ // In this case we don't even need to compute the combining character.
+ return accent;
+ }
+
int combining = sAccentToCombining.get(accent);
if (combining == 0) {
return 0;
@@ -495,7 +506,8 @@ public class KeyCharacterMap implements Parcelable {
sDeadKeyBuilder.append((char)c);
sDeadKeyBuilder.append((char)combining);
String result = Normalizer.normalize(sDeadKeyBuilder, Normalizer.Form.NFC);
- combined = result.length() == 1 ? result.charAt(0) : 0;
+ combined = result.codePointCount(0, result.length()) == 1
+ ? result.codePointAt(0) : 0;
sDeadKeyCache.put(combination, combined);
}
}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index c2a3e58..0546d24 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -302,27 +302,27 @@ public class KeyEvent extends InputEvent implements Parcelable {
public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana)
/** Key code constant: A Button key.
* On a game controller, the A button should be either the button labeled A
- * or the first button on the upper row of controller buttons. */
+ * or the first button on the bottom row of controller buttons. */
public static final int KEYCODE_BUTTON_A = 96;
/** Key code constant: B Button key.
* On a game controller, the B button should be either the button labeled B
- * or the second button on the upper row of controller buttons. */
+ * or the second button on the bottom row of controller buttons. */
public static final int KEYCODE_BUTTON_B = 97;
/** Key code constant: C Button key.
* On a game controller, the C button should be either the button labeled C
- * or the third button on the upper row of controller buttons. */
+ * or the third button on the bottom row of controller buttons. */
public static final int KEYCODE_BUTTON_C = 98;
/** Key code constant: X Button key.
* On a game controller, the X button should be either the button labeled X
- * or the first button on the lower row of controller buttons. */
+ * or the first button on the upper row of controller buttons. */
public static final int KEYCODE_BUTTON_X = 99;
/** Key code constant: Y Button key.
* On a game controller, the Y button should be either the button labeled Y
- * or the second button on the lower row of controller buttons. */
+ * or the second button on the upper row of controller buttons. */
public static final int KEYCODE_BUTTON_Y = 100;
/** Key code constant: Z Button key.
* On a game controller, the Z button should be either the button labeled Z
- * or the third button on the lower row of controller buttons. */
+ * or the third button on the upper row of controller buttons. */
public static final int KEYCODE_BUTTON_Z = 101;
/** Key code constant: L1 Button key.
* On a game controller, the L1 button should be either the button labeled L1 (or L)
@@ -623,8 +623,14 @@ public class KeyEvent extends InputEvent implements Parcelable {
/** Key code constant: Assist key.
* Launches the global assist activity. Not delivered to applications. */
public static final int KEYCODE_ASSIST = 219;
+ /** Key code constant: Brightness Down key.
+ * Adjusts the screen brightness down. */
+ public static final int KEYCODE_BRIGHTNESS_DOWN = 220;
+ /** Key code constant: Brightness Up key.
+ * Adjusts the screen brightness up. */
+ public static final int KEYCODE_BRIGHTNESS_UP = 221;
- private static final int LAST_KEYCODE = KEYCODE_ASSIST;
+ private static final int LAST_KEYCODE = KEYCODE_BRIGHTNESS_UP;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
@@ -866,6 +872,8 @@ public class KeyEvent extends InputEvent implements Parcelable {
names.append(KEYCODE_RO, "KEYCODE_RO");
names.append(KEYCODE_KANA, "KEYCODE_KANA");
names.append(KEYCODE_ASSIST, "KEYCODE_ASSIST");
+ names.append(KEYCODE_BRIGHTNESS_DOWN, "KEYCODE_BRIGHTNESS_DOWN");
+ names.append(KEYCODE_BRIGHTNESS_UP, "KEYCODE_BRIGHTNESS_UP");
};
// Symbolic names of all metakeys in bit order from least significant to most significant.
@@ -1225,6 +1233,12 @@ public class KeyEvent extends InputEvent implements Parcelable {
public static final int FLAG_FALLBACK = 0x400;
/**
+ * Signifies that the key is being predispatched.
+ * @hide
+ */
+ public static final int FLAG_PREDISPATCH = 0x20000000;
+
+ /**
* Private control to determine when an app is tracking a key sequence.
* @hide
*/
@@ -2808,7 +2822,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
*
* @param symbolicName The symbolic name of the keycode.
* @return The keycode or {@link #KEYCODE_UNKNOWN} if not found.
- * @see #keycodeToString
+ * @see #keycodeToString(int)
*/
public static int keyCodeFromString(String symbolicName) {
if (symbolicName == null) {
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 26a5b26..aa43bad 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -19,6 +19,7 @@ package android.view;
import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
+import android.os.Trace;
import android.widget.FrameLayout;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -423,6 +424,8 @@ public abstract class LayoutInflater {
*/
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
+
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
@@ -520,6 +523,8 @@ public abstract class LayoutInflater {
mConstructorArgs[1] = null;
}
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+
return result;
}
}
@@ -547,6 +552,8 @@ public abstract class LayoutInflater {
Class<? extends View> clazz = null;
try {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
+
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = mContext.getClassLoader().loadClass(
@@ -615,6 +622,8 @@ public abstract class LayoutInflater {
+ (clazz == null ? "<unknown>" : clazz.getName()));
ie.initCause(e);
throw ie;
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
@@ -645,7 +654,7 @@ public abstract class LayoutInflater {
/**
* Version of {@link #onCreateView(String, AttributeSet)} that also
- * takes the future parent of the view being constructure. The default
+ * takes the future parent of the view being constructed. The default
* implementation simply calls {@link #onCreateView(String, AttributeSet)}.
*
* @param parent The future parent of the returned view. <em>Note that
diff --git a/core/java/android/view/WindowInfo.aidl b/core/java/android/view/MagnificationSpec.aidl
index 23e927a..d5fbdef 100644
--- a/core/java/android/view/WindowInfo.aidl
+++ b/core/java/android/view/MagnificationSpec.aidl
@@ -17,4 +17,4 @@
package android.view;
-parcelable WindowInfo;
+parcelable MagnificationSpec;
diff --git a/core/java/android/view/MagnificationSpec.java b/core/java/android/view/MagnificationSpec.java
new file mode 100644
index 0000000..0ee6714
--- /dev/null
+++ b/core/java/android/view/MagnificationSpec.java
@@ -0,0 +1,123 @@
+/*
+ * 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.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pools.SynchronizedPool;
+
+/**
+ * This class represents spec for performing screen magnification.
+ *
+ * @hide
+ */
+public class MagnificationSpec implements Parcelable {
+ private static final int MAX_POOL_SIZE = 20;
+ private static final SynchronizedPool<MagnificationSpec> sPool =
+ new SynchronizedPool<MagnificationSpec>(MAX_POOL_SIZE);
+
+ public float scale = 1.0f;
+ public float offsetX;
+ public float offsetY;
+
+ private MagnificationSpec() {
+ /* do nothing - reducing visibility */
+ }
+
+ public void initialize(float scale, float offsetX, float offsetY) {
+ if (scale < 1) {
+ throw new IllegalArgumentException("Scale must be greater than or equal to one!");
+ }
+ this.scale = scale;
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ }
+
+ public boolean isNop() {
+ return scale == 1.0f && offsetX == 0 && offsetY == 0;
+ }
+
+ public static MagnificationSpec obtain(MagnificationSpec other) {
+ MagnificationSpec info = obtain();
+ info.scale = other.scale;
+ info.offsetX = other.offsetX;
+ info.offsetY = other.offsetY;
+ return info;
+ }
+
+ public static MagnificationSpec obtain() {
+ MagnificationSpec spec = sPool.acquire();
+ return (spec != null) ? spec : new MagnificationSpec();
+ }
+
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ public void clear() {
+ scale = 1.0f;
+ offsetX = 0.0f;
+ offsetY = 0.0f;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeFloat(scale);
+ parcel.writeFloat(offsetX);
+ parcel.writeFloat(offsetY);
+ recycle();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<scale:");
+ builder.append(scale);
+ builder.append(",offsetX:");
+ builder.append(offsetX);
+ builder.append(",offsetY:");
+ builder.append(offsetY);
+ builder.append(">");
+ return builder.toString();
+ }
+
+ private void initFromParcel(Parcel parcel) {
+ scale = parcel.readFloat();
+ offsetX = parcel.readFloat();
+ offsetY = parcel.readFloat();
+ }
+
+ public static final Creator<MagnificationSpec> CREATOR = new Creator<MagnificationSpec>() {
+ @Override
+ public MagnificationSpec[] newArray(int size) {
+ return new MagnificationSpec[size];
+ }
+
+ @Override
+ public MagnificationSpec createFromParcel(Parcel parcel) {
+ MagnificationSpec spec = MagnificationSpec.obtain();
+ spec.initFromParcel(parcel);
+ return spec;
+ }
+ };
+}
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 78fa2d7..ee36097 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1947,8 +1947,6 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* @see #TOOL_TYPE_FINGER
* @see #TOOL_TYPE_STYLUS
* @see #TOOL_TYPE_MOUSE
- * @see #TOOL_TYPE_INDIRECT_FINGER
- * @see #TOOL_TYPE_INDIRECT_STYLUS
*/
public final int getToolType(int pointerIndex) {
return nativeGetToolType(mNativePtr, pointerIndex);
@@ -2190,7 +2188,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* on the screen, before it had been adjusted for the containing window
* and views.
*
- * @see getX()
+ * @see #getX(int)
* @see #AXIS_X
*/
public final float getRawX() {
@@ -2203,7 +2201,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
* on the screen, before it had been adjusted for the containing window
* and views.
*
- * @see getY()
+ * @see #getY(int)
* @see #AXIS_Y
*/
public final float getRawY() {
@@ -3063,7 +3061,7 @@ public final class MotionEvent extends InputEvent implements Parcelable {
*
* @param symbolicName The symbolic name of the axis.
* @return The axis or -1 if not found.
- * @see #keycodeToString
+ * @see KeyEvent#keycodeToString(int)
*/
public static int axisFromString(String symbolicName) {
if (symbolicName == null) {
diff --git a/core/java/android/view/SimulatedDpad.java b/core/java/android/view/SimulatedDpad.java
deleted file mode 100644
index b03e4c7..0000000
--- a/core/java/android/view/SimulatedDpad.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.app.SearchManager;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.util.Log;
-
-/**
- * This class creates DPAD events from touchpad events.
- *
- * @see ViewRootImpl
- */
-
-//TODO: Make this class an internal class of ViewRootImpl.java
-class SimulatedDpad {
-
- private static final String TAG = "SimulatedDpad";
-
- // Maximum difference in milliseconds between the down and up of a touch
- // event for it to be considered a tap
- // TODO:Read this value from a configuration file
- private static final int MAX_TAP_TIME = 250;
- // Where the cutoff is for determining an edge swipe
- private static final float EDGE_SWIPE_THRESHOLD = 0.9f;
- private static final int MSG_FLICK = 313;
- // TODO: Pass touch slop from the input device
- private static final int TOUCH_SLOP = 30;
- // The position of the previous touchpad event
- private float mLastTouchpadXPosition;
- private float mLastTouchpadYPosition;
- // Where the touchpad was initially pressed
- private float mTouchpadEnterXPosition;
- private float mTouchpadEnterYPosition;
- // When the most recent ACTION_HOVER_ENTER occurred
- private long mLastTouchPadStartTimeMs = 0;
- // When the most recent direction key was sent
- private long mLastTouchPadKeySendTimeMs = 0;
- // When the most recent touch event of any type occurred
- private long mLastTouchPadEventTimeMs = 0;
- // Did the swipe begin in a valid region
- private boolean mEdgeSwipePossible;
-
- private final Context mContext;
-
- // How quickly keys were sent;
- private int mKeySendRateMs = 0;
- private int mLastKeySent;
- // Last movement in device screen pixels
- private float mLastMoveX = 0;
- private float mLastMoveY = 0;
- // Offset from the initial touch. Gets reset as direction keys are sent.
- private float mAccumulatedX;
- private float mAccumulatedY;
-
- // Change in position allowed during tap events
- private float mTouchSlop;
- private float mTouchSlopSquared;
- // Has the TouchSlop constraint been invalidated
- private boolean mAlwaysInTapRegion = true;
-
- // Information from the most recent event.
- // Used to determine what device sent the event during a fling.
- private int mLastSource;
- private int mLastMetaState;
- private int mLastDeviceId;
-
- // TODO: Currently using screen dimensions tuned to a Galaxy Nexus, need to
- // read this from a config file instead
- private int mDistancePerTick;
- private int mDistancePerTickSquared;
- // Highest rate that the flinged events can occur at before dying out
- private int mMaxRepeatDelay;
- // The square of the minimum distance needed for a flick to register
- private int mMinFlickDistanceSquared;
- // How quickly the repeated events die off
- private float mFlickDecay;
-
- public SimulatedDpad(Context context) {
- mDistancePerTick = SystemProperties.getInt("persist.vr_dist_tick", 64);
- mDistancePerTickSquared = mDistancePerTick * mDistancePerTick;
- mMaxRepeatDelay = SystemProperties.getInt("persist.vr_repeat_delay", 300);
- mMinFlickDistanceSquared = SystemProperties.getInt("persist.vr_min_flick", 20);
- mMinFlickDistanceSquared *= mMinFlickDistanceSquared;
- mFlickDecay = Float.parseFloat(SystemProperties.get(
- "persist.sys.vr_flick_decay", "1.3"));
- mTouchSlop = TOUCH_SLOP;
- mTouchSlopSquared = mTouchSlop * mTouchSlop;
-
- mContext = context;
- }
-
- private final Handler mHandler = new Handler(true /*async*/) {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_FLICK: {
- final long time = SystemClock.uptimeMillis();
- ViewRootImpl viewroot = (ViewRootImpl) msg.obj;
- // Send the key
- viewroot.enqueueInputEvent(new KeyEvent(time, time,
- KeyEvent.ACTION_DOWN, msg.arg2, 0, mLastMetaState,
- mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource));
- viewroot.enqueueInputEvent(new KeyEvent(time, time,
- KeyEvent.ACTION_UP, msg.arg2, 0, mLastMetaState,
- mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource));
-
- // Increase the delay by the decay factor and resend
- final int delay = (int) Math.ceil(mFlickDecay * msg.arg1);
- if (delay <= mMaxRepeatDelay) {
- Message msgCopy = Message.obtain(msg);
- msgCopy.arg1 = delay;
- msgCopy.setAsynchronous(true);
- mHandler.sendMessageDelayed(msgCopy, delay);
- }
- break;
- }
- }
- }
- };
-
- public void updateTouchPad(ViewRootImpl viewroot, MotionEvent event,
- boolean synthesizeNewKeys) {
- if (!synthesizeNewKeys) {
- mHandler.removeMessages(MSG_FLICK);
- }
- InputDevice device = event.getDevice();
- if (device == null) {
- return;
- }
- // Store what time the touchpad event occurred
- final long time = SystemClock.uptimeMillis();
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mLastTouchPadStartTimeMs = time;
- mAlwaysInTapRegion = true;
- mTouchpadEnterXPosition = event.getX();
- mTouchpadEnterYPosition = event.getY();
- mAccumulatedX = 0;
- mAccumulatedY = 0;
- mLastMoveX = 0;
- mLastMoveY = 0;
- if (device.getMotionRange(MotionEvent.AXIS_Y).getMax()
- * EDGE_SWIPE_THRESHOLD < event.getY()) {
- // Did the swipe begin in a valid region
- mEdgeSwipePossible = true;
- }
- // Clear any flings
- if (synthesizeNewKeys) {
- mHandler.removeMessages(MSG_FLICK);
- }
- break;
- case MotionEvent.ACTION_MOVE:
- // Determine whether the move is slop or an intentional move
- float deltaX = event.getX() - mTouchpadEnterXPosition;
- float deltaY = event.getY() - mTouchpadEnterYPosition;
- if (mTouchSlopSquared < deltaX * deltaX + deltaY * deltaY) {
- mAlwaysInTapRegion = false;
- }
- // Checks if the swipe has crossed the midpoint
- // and if our swipe gesture is complete
- if (event.getY() < (device.getMotionRange(MotionEvent.AXIS_Y).getMax()
- * .5) && mEdgeSwipePossible) {
- mEdgeSwipePossible = false;
-
- Intent intent =
- ((SearchManager)mContext.getSystemService(Context.SEARCH_SERVICE))
- .getAssistIntent(mContext, UserHandle.USER_CURRENT_OR_SELF);
- if (intent != null) {
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- try {
- mContext.startActivity(intent);
- } catch (ActivityNotFoundException e){
- Log.e(TAG, "Could not start search activity");
- }
- } else {
- Log.e(TAG, "Could not find a search activity");
- }
- }
- // Find the difference in position between the two most recent
- // touchpad events
- mLastMoveX = event.getX() - mLastTouchpadXPosition;
- mLastMoveY = event.getY() - mLastTouchpadYPosition;
- mAccumulatedX += mLastMoveX;
- mAccumulatedY += mLastMoveY;
- float mAccumulatedXSquared = mAccumulatedX * mAccumulatedX;
- float mAccumulatedYSquared = mAccumulatedY * mAccumulatedY;
- // Determine if we've moved far enough to send a key press
- if (mAccumulatedXSquared > mDistancePerTickSquared ||
- mAccumulatedYSquared > mDistancePerTickSquared) {
- float dominantAxis;
- float sign;
- boolean isXAxis;
- int key;
- int repeatCount = 0;
- // Determine dominant axis
- if (mAccumulatedXSquared > mAccumulatedYSquared) {
- dominantAxis = mAccumulatedX;
- isXAxis = true;
- } else {
- dominantAxis = mAccumulatedY;
- isXAxis = false;
- }
- // Determine sign of axis
- sign = (dominantAxis > 0) ? 1 : -1;
- // Determine key to send
- if (isXAxis) {
- key = (sign == 1) ? KeyEvent.KEYCODE_DPAD_RIGHT :
- KeyEvent.KEYCODE_DPAD_LEFT;
- } else {
- key = (sign == 1) ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
- }
- // Send key until maximum distance constraint is satisfied
- while (dominantAxis * dominantAxis > mDistancePerTickSquared) {
- repeatCount++;
- dominantAxis -= sign * mDistancePerTick;
- if (synthesizeNewKeys) {
- viewroot.enqueueInputEvent(new KeyEvent(time, time,
- KeyEvent.ACTION_DOWN, key, 0, event.getMetaState(),
- event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK,
- event.getSource()));
- viewroot.enqueueInputEvent(new KeyEvent(time, time,
- KeyEvent.ACTION_UP, key, 0, event.getMetaState(),
- event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK,
- event.getSource()));
- }
- }
- // Save new axis values
- mAccumulatedX = isXAxis ? dominantAxis : 0;
- mAccumulatedY = isXAxis ? 0 : dominantAxis;
-
- mLastKeySent = key;
- mKeySendRateMs = (int) ((time - mLastTouchPadKeySendTimeMs) / repeatCount);
- mLastTouchPadKeySendTimeMs = time;
- }
- break;
- case MotionEvent.ACTION_UP:
- if (time - mLastTouchPadStartTimeMs < MAX_TAP_TIME && mAlwaysInTapRegion) {
- if (synthesizeNewKeys) {
- viewroot.enqueueInputEvent(new KeyEvent(mLastTouchPadStartTimeMs, time,
- KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0,
- event.getMetaState(), event.getDeviceId(), 0,
- KeyEvent.FLAG_FALLBACK, event.getSource()));
- viewroot.enqueueInputEvent(new KeyEvent(mLastTouchPadStartTimeMs, time,
- KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0,
- event.getMetaState(), event.getDeviceId(), 0,
- KeyEvent.FLAG_FALLBACK, event.getSource()));
- }
- } else {
- float xMoveSquared = mLastMoveX * mLastMoveX;
- float yMoveSquared = mLastMoveY * mLastMoveY;
- // Determine whether the last gesture was a fling.
- if (mMinFlickDistanceSquared <= xMoveSquared + yMoveSquared &&
- time - mLastTouchPadEventTimeMs <= MAX_TAP_TIME &&
- mKeySendRateMs <= mMaxRepeatDelay && mKeySendRateMs > 0) {
- mLastDeviceId = event.getDeviceId();
- mLastSource = event.getSource();
- mLastMetaState = event.getMetaState();
-
- if (synthesizeNewKeys) {
- Message message = Message.obtain(mHandler, MSG_FLICK,
- mKeySendRateMs, mLastKeySent, viewroot);
- message.setAsynchronous(true);
- mHandler.sendMessageDelayed(message, mKeySendRateMs);
- }
- }
- }
- mEdgeSwipePossible = false;
- break;
- }
-
- // Store touch event position and time
- mLastTouchPadEventTimeMs = time;
- mLastTouchpadXPosition = event.getX();
- mLastTouchpadYPosition = event.getY();
- }
-}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 0a81a71..ae4005b 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -16,20 +16,15 @@
package android.view;
-import dalvik.system.CloseGuard;
-
import android.content.res.CompatibilityInfo.Translator;
-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;
+import android.os.Parcelable;
import android.util.Log;
+import dalvik.system.CloseGuard;
/**
* Handle onto a raw buffer that is being managed by the screen compositor.
@@ -37,11 +32,23 @@ import android.util.Log;
public class Surface implements Parcelable {
private static final String TAG = "Surface";
- private static final boolean HEADLESS = "1".equals(
- SystemProperties.get("ro.config.headless", "0"));
+ private static native int nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture)
+ throws OutOfResourcesException;
+ private static native int nativeCreateFromSurfaceControl(int surfaceControlNativeObject);
+
+ private static native void nativeLockCanvas(int nativeObject, Canvas canvas, Rect dirty)
+ throws OutOfResourcesException;
+ private static native void nativeUnlockCanvasAndPost(int nativeObject, Canvas canvas);
+
+ private static native void nativeRelease(int nativeObject);
+ private static native boolean nativeIsValid(int nativeObject);
+ private static native boolean nativeIsConsumerRunningBehind(int nativeObject);
+ private static native int nativeReadFromParcel(int nativeObject, Parcel source);
+ private static native void nativeWriteToParcel(int nativeObject, Parcel dest);
public static final Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
+ @Override
public Surface createFromParcel(Parcel source) {
try {
Surface s = new Surface();
@@ -53,11 +60,25 @@ public class Surface implements Parcelable {
}
}
+ @Override
public Surface[] newArray(int size) {
return new Surface[size];
}
};
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ // Guarded state.
+ final Object mLock = new Object(); // protects the native state
+ private String mName;
+ int mNativeSurface; // package scope only for SurfaceControl access
+ private int mGenerationId; // incremented each time mNativeSurface changes
+ private final Canvas mCanvas = new CompatibleCanvas();
+
+ // A matrix to scale the matrix set by application. This is set to null for
+ // non compatibility mode.
+ private Matrix mCompatibleMatrix;
+
/**
* Rotation constant: 0 degree rotation (natural orientation)
*/
@@ -78,266 +99,11 @@ public class Surface implements Parcelable {
*/
public static final int ROTATION_270 = 3;
- /* built-in physical display ids (keep in sync with ISurfaceComposer.h)
- * these are different from the logical display ids used elsewhere in the framework */
-
- /**
- * Built-in physical display id: Main display.
- * Use only with {@link #getBuiltInDisplay()}.
- * @hide
- */
- 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 ISurfaceComposerClient.h) */
-
- /**
- * Surface creation flag: Surface is created hidden
- * @hide */
- public static final int HIDDEN = 0x00000004;
-
- /**
- * 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;
-
- /**
- * 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:
- *
- * 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;
-
- /**
- * 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;
-
- /**
- * 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;
-
- // 0x1000 is reserved for an independent DRM protected flag in framework
-
- /**
- * Surface creation flag: Creates a normal surface.
- * This is the default.
- * @hide
- */
- public static final int FX_SURFACE_NORMAL = 0x00000000;
-
- /**
- * 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;
-
- /**
- * 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;
-
- /**
- * @hide
- */
- public static final int FX_SURFACE_SCREENSHOT = 0x00030000;
-
- /**
- * 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) */
-
- /**
- * Surface flag: Hide the surface.
- * Equivalent to calling hide().
- * @hide
- */
- public static final int SURFACE_HIDDEN = 0x01;
-
-
- 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 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.
- private Translator mCompatibilityTranslator;
-
- // A matrix to scale the matrix set by application. This is set to null for
- // non compatibility mode.
- private Matrix mCompatibleMatrix;
-
- private int mWidth;
- private int mHeight;
-
- 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 static native void nativeSetAnimationTransaction();
-
- 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, boolean secure);
- 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 static native void nativeBlankDisplay(IBinder displayToken);
- private static native void nativeUnblankDisplay(IBinder displayToken);
-
- private native void nativeCopyFrom(Surface other);
- private native void nativeTransferFrom(Surface other);
- private native void nativeReadFromParcel(Parcel source);
- private native void nativeWriteToParcel(Parcel dest);
-
-
/**
* Create an empty surface, which will later be filled in by readFromParcel().
* @hide
*/
public Surface() {
- checkHeadless();
-
- mCloseGuard.open("release");
- }
-
- /**
- * 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 ((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());
- }
-
- checkHeadless();
-
- mName = name;
- mWidth = w;
- mHeight = h;
- nativeCreate(session, name, w, h, format, flags);
-
- mCloseGuard.open("release");
}
/**
@@ -355,17 +121,22 @@ public class Surface implements Parcelable {
throw new IllegalArgumentException("surfaceTexture must not be null");
}
- checkHeadless();
-
- 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);
+ synchronized (mLock) {
+ mName = surfaceTexture.toString();
+ try {
+ setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
+ } catch (OutOfResourcesException ex) {
+ // We can't throw OutOfResourcesException because it would be an API change.
+ throw new RuntimeException(ex);
+ }
}
+ }
- mCloseGuard.open("release");
+ /* called from android_view_Surface_createFromIGraphicBufferProducer() */
+ private Surface(int nativeObject) {
+ synchronized (mLock) {
+ setNativeObjectLocked(nativeObject);
+ }
}
@Override
@@ -374,7 +145,7 @@ public class Surface implements Parcelable {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
- nativeRelease();
+ release();
} finally {
super.finalize();
}
@@ -386,8 +157,12 @@ public class Surface implements Parcelable {
* This will make the surface invalid.
*/
public void release() {
- nativeRelease();
- mCloseGuard.close();
+ synchronized (mLock) {
+ if (mNativeSurface != 0) {
+ nativeRelease(mNativeSurface);
+ setNativeObjectLocked(0);
+ }
+ }
}
/**
@@ -397,8 +172,7 @@ public class Surface implements Parcelable {
* @hide
*/
public void destroy() {
- nativeDestroy();
- mCloseGuard.close();
+ release();
}
/**
@@ -408,7 +182,10 @@ public class Surface implements Parcelable {
* Otherwise returns false.
*/
public boolean isValid() {
- return nativeIsValid();
+ synchronized (mLock) {
+ if (mNativeSurface == 0) return false;
+ return nativeIsValid(mNativeSurface);
+ }
}
/**
@@ -419,7 +196,9 @@ public class Surface implements Parcelable {
* @hide
*/
public int getGenerationId() {
- return mGenerationId;
+ synchronized (mLock) {
+ return mGenerationId;
+ }
}
/**
@@ -429,27 +208,34 @@ public class Surface implements Parcelable {
* @hide
*/
public boolean isConsumerRunningBehind() {
- return nativeIsConsumerRunningBehind();
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+ return nativeIsConsumerRunningBehind(mNativeSurface);
+ }
}
/**
* Gets a {@link Canvas} for drawing into this surface.
*
- * After drawing into the provided {@link Canvas}, the caller should
+ * After drawing into the provided {@link Canvas}, the caller must
* invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
*
- * @param dirty A rectangle that represents the dirty region that the caller wants
+ * @param inOutDirty 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.
+ * not available. The caller must redraw the entire dirty region as represented
+ * by the contents of the inOutDirty rectangle 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.
*/
- public Canvas lockCanvas(Rect dirty)
+ public Canvas lockCanvas(Rect inOutDirty)
throws OutOfResourcesException, IllegalArgumentException {
- return nativeLockCanvas(dirty);
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+ nativeLockCanvas(mNativeSurface, mCanvas, inOutDirty);
+ return mCanvas;
+ }
}
/**
@@ -459,7 +245,15 @@ public class Surface implements Parcelable {
* @param canvas The canvas previously obtained from {@link #lockCanvas}.
*/
public void unlockCanvasAndPost(Canvas canvas) {
- nativeUnlockCanvasAndPost(canvas);
+ if (canvas != mCanvas) {
+ throw new IllegalArgumentException("canvas object must be the same instance that "
+ + "was previously returned by lockCanvas");
+ }
+
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+ nativeUnlockCanvasAndPost(mNativeSurface, canvas);
+ }
}
/**
@@ -483,203 +277,6 @@ public class Surface implements Parcelable {
}
/**
- * Like {@link #screenshot(int, int, int, int)} but includes all
- * Surfaces in the screenshot.
- *
- * @hide
- */
- 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.
- *
- * @param width The desired width of the returned bitmap; the raw
- * screen will be scaled down to this size.
- * @param height The desired height of the returned bitmap; the raw
- * screen will be scaled down to this size.
- * @param minLayer The lowest (bottom-most Z order) surface layer to
- * 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, or null
- * if an error occurs.
- *
- * @hide
- */
- 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 void openTransaction() {
- nativeOpenTransaction();
- }
-
- /** end a transaction @hide */
- public static void closeTransaction() {
- nativeCloseTransaction();
- }
-
- /** flag the transaction as an animation @hide */
- public static void setAnimationTransaction() {
- nativeSetAnimationTransaction();
- }
-
- /** @hide */
- public void setLayer(int zorder) {
- nativeSetLayer(zorder);
- }
-
- /** @hide */
- public void setPosition(int x, int y) {
- nativeSetPosition(x, y);
- }
-
- /** @hide */
- public void setPosition(float x, float y) {
- nativeSetPosition(x, y);
- }
-
- /** @hide */
- public void setSize(int w, int h) {
- mWidth = w;
- mHeight = h;
- nativeSetSize(w, h);
- }
-
- /** @hide */
- public int getWidth() {
- return mWidth;
- }
-
- /** @hide */
- public int getHeight() {
- return mHeight;
- }
-
- /** @hide */
- public void hide() {
- nativeSetFlags(SURFACE_HIDDEN, SURFACE_HIDDEN);
- }
-
- /** @hide */
- 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 void setFlags(int flags, int mask) {
- nativeSetFlags(flags, mask);
- }
-
- /** @hide */
- public void setWindowCrop(Rect crop) {
- nativeSetWindowCrop(crop);
- }
-
- /** @hide */
- public void setLayerStack(int layerStack) {
- nativeSetLayerStack(layerStack);
- }
-
- /** @hide */
- public static IBinder getBuiltInDisplay(int builtInDisplayId) {
- return nativeGetBuiltInDisplay(builtInDisplayId);
- }
-
- /** @hide */
- public static IBinder createDisplay(String name, boolean secure) {
- if (name == null) {
- throw new IllegalArgumentException("name must not be null");
- }
- return nativeCreateDisplay(name, secure);
- }
-
- /** @hide */
- public static void setDisplaySurface(IBinder displayToken, Surface surface) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- nativeSetDisplaySurface(displayToken, surface);
- }
-
- /** @hide */
- public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- nativeSetDisplayLayerStack(displayToken, layerStack);
- }
-
- /** @hide */
- 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);
- }
-
- /** @hide */
- public static void blankDisplay(IBinder displayToken) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- nativeBlankDisplay(displayToken);
- }
-
- /** @hide */
- public static void unblankDisplay(IBinder displayToken) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- nativeUnblankDisplay(displayToken);
- }
-
- /**
* 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
@@ -688,29 +285,50 @@ public class Surface implements Parcelable {
* in to it.
* @hide
*/
- public void copyFrom(Surface other) {
+ public void copyFrom(SurfaceControl other) {
if (other == null) {
throw new IllegalArgumentException("other must not be null");
}
- if (other != this) {
- nativeCopyFrom(other);
+
+ int surfaceControlPtr = other.mNativeObject;
+ if (surfaceControlPtr == 0) {
+ throw new NullPointerException(
+ "SurfaceControl native object is null. Are you using a released SurfaceControl?");
+ }
+ int newNativeObject = nativeCreateFromSurfaceControl(surfaceControlPtr);
+
+ synchronized (mLock) {
+ if (mNativeSurface != 0) {
+ nativeRelease(mNativeSurface);
+ }
+ setNativeObjectLocked(newNativeObject);
}
}
/**
- * 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.
+ * This is intended to be used by {@link SurfaceView#updateWindow} only.
+ * @param other access is not thread safe
+ * @hide
+ * @deprecated
*/
+ @Deprecated
public void transferFrom(Surface other) {
if (other == null) {
throw new IllegalArgumentException("other must not be null");
}
if (other != this) {
- nativeTransferFrom(other);
+ final int newPtr;
+ synchronized (other.mLock) {
+ newPtr = other.mNativeSurface;
+ other.setNativeObjectLocked(0);
+ }
+
+ synchronized (mLock) {
+ if (mNativeSurface != 0) {
+ nativeRelease(mNativeSurface);
+ }
+ setNativeObjectLocked(newPtr);
+ }
}
}
@@ -724,8 +342,10 @@ public class Surface implements Parcelable {
throw new IllegalArgumentException("source must not be null");
}
- mName = source.readString();
- nativeReadFromParcel(source);
+ synchronized (mLock) {
+ mName = source.readString();
+ setNativeObjectLocked(nativeReadFromParcel(mNativeSurface, source));
+ }
}
@Override
@@ -733,9 +353,10 @@ public class Surface implements Parcelable {
if (dest == null) {
throw new IllegalArgumentException("dest must not be null");
}
-
- dest.writeString(mName);
- nativeWriteToParcel(dest);
+ synchronized (mLock) {
+ dest.writeString(mName);
+ nativeWriteToParcel(mNativeSurface, dest);
+ }
if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
release();
}
@@ -743,12 +364,26 @@ public class Surface implements Parcelable {
@Override
public String toString() {
- return "Surface(name=" + mName + ", identity=" + nativeGetIdentity() + ")";
+ synchronized (mLock) {
+ return "Surface(name=" + mName + ")";
+ }
+ }
+
+ private void setNativeObjectLocked(int ptr) {
+ if (mNativeSurface != ptr) {
+ if (mNativeSurface == 0 && ptr != 0) {
+ mCloseGuard.open("release");
+ } else if (mNativeSurface != 0 && ptr == 0) {
+ mCloseGuard.close();
+ }
+ mNativeSurface = ptr;
+ mGenerationId += 1;
+ }
}
- private static void checkHeadless() {
- if (HEADLESS) {
- throw new UnsupportedOperationException("Device is headless");
+ private void checkNotReleasedLocked() {
+ if (mNativeSurface == 0) {
+ throw new IllegalStateException("Surface has already been released.");
}
}
@@ -758,69 +393,36 @@ public class Surface implements Parcelable {
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.
+ * Returns a human readable representation of a rotation.
+ *
+ * @param rotation The rotation.
+ * @return The rotation symbolic name.
+ *
* @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 boolean secure;
-
- public PhysicalDisplayInfo() {
- }
-
- public PhysicalDisplayInfo(PhysicalDisplayInfo other) {
- copyFrom(other);
- }
-
- @Override
- public boolean equals(Object o) {
- return o instanceof PhysicalDisplayInfo && equals((PhysicalDisplayInfo)o);
- }
-
- public boolean equals(PhysicalDisplayInfo other) {
- return other != null
- && width == other.width
- && height == other.height
- && refreshRate == other.refreshRate
- && density == other.density
- && xDpi == other.xDpi
- && yDpi == other.yDpi
- && secure == other.secure;
- }
-
- @Override
- public int hashCode() {
- return 0; // don't care
- }
-
- public void copyFrom(PhysicalDisplayInfo other) {
- width = other.width;
- height = other.height;
- refreshRate = other.refreshRate;
- density = other.density;
- xDpi = other.xDpi;
- yDpi = other.yDpi;
- secure = other.secure;
- }
-
- // For debugging purposes
- @Override
- public String toString() {
- return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, "
- + "density " + density + ", " + xDpi + " x " + yDpi + " dpi, secure " + secure
- + "}";
+ public static String rotationToString(int rotation) {
+ switch (rotation) {
+ case Surface.ROTATION_0: {
+ return "ROTATION_0";
+ }
+ case Surface.ROTATION_90: {
+ return "ROATATION_90";
+ }
+ case Surface.ROTATION_180: {
+ return "ROATATION_180";
+ }
+ case Surface.ROTATION_270: {
+ return "ROATATION_270";
+ }
+ default: {
+ throw new IllegalArgumentException("Invalid rotation: " + rotation);
+ }
}
}
@@ -844,24 +446,6 @@ public class Surface implements Parcelable {
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;
- }
-
- @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
@@ -874,6 +458,7 @@ public class Surface implements Parcelable {
}
}
+ @SuppressWarnings("deprecation")
@Override
public void getMatrix(Matrix m) {
super.getMatrix(m);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
new file mode 100644
index 0000000..c6da84f
--- /dev/null
+++ b/core/java/android/view/SurfaceControl.java
@@ -0,0 +1,628 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import dalvik.system.CloseGuard;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.Surface;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.util.Log;
+
+/**
+ * SurfaceControl
+ * @hide
+ */
+public class SurfaceControl {
+ private static final String TAG = "SurfaceControl";
+
+ private static native int nativeCreate(SurfaceSession session, String name,
+ int w, int h, int format, int flags)
+ throws OutOfResourcesException;
+ private static native void nativeRelease(int nativeObject);
+ private static native void nativeDestroy(int nativeObject);
+
+ private static native Bitmap nativeScreenshot(IBinder displayToken,
+ int width, int height, int minLayer, int maxLayer, boolean allLayers);
+ private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
+ int width, int height, int minLayer, int maxLayer, boolean allLayers);
+
+ private static native void nativeOpenTransaction();
+ private static native void nativeCloseTransaction();
+ private static native void nativeSetAnimationTransaction();
+
+ private static native void nativeSetLayer(int nativeObject, int zorder);
+ private static native void nativeSetPosition(int nativeObject, float x, float y);
+ private static native void nativeSetSize(int nativeObject, int w, int h);
+ private static native void nativeSetTransparentRegionHint(int nativeObject, Region region);
+ private static native void nativeSetAlpha(int nativeObject, float alpha);
+ private static native void nativeSetMatrix(int nativeObject, float dsdx, float dtdx, float dsdy, float dtdy);
+ private static native void nativeSetFlags(int nativeObject, int flags, int mask);
+ private static native void nativeSetWindowCrop(int nativeObject, int l, int t, int r, int b);
+ private static native void nativeSetLayerStack(int nativeObject, int layerStack);
+
+ private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
+ private static native IBinder nativeCreateDisplay(String name, boolean secure);
+ private static native void nativeSetDisplaySurface(
+ IBinder displayToken, int nativeSurfaceObject);
+ private static native void nativeSetDisplayLayerStack(
+ IBinder displayToken, int layerStack);
+ private static native void nativeSetDisplayProjection(
+ IBinder displayToken, int orientation,
+ int l, int t, int r, int b,
+ int L, int T, int R, int B);
+ private static native boolean nativeGetDisplayInfo(
+ IBinder displayToken, SurfaceControl.PhysicalDisplayInfo outInfo);
+ private static native void nativeBlankDisplay(IBinder displayToken);
+ private static native void nativeUnblankDisplay(IBinder displayToken);
+
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private String mName;
+ int mNativeObject; // package visibility only for Surface.java access
+
+ private static final boolean HEADLESS = "1".equals(
+ SystemProperties.get("ro.config.headless", "0"));
+
+ /**
+ * Exception thrown when a surface couldn't be created or resized.
+ */
+ public static class OutOfResourcesException extends Exception {
+ public OutOfResourcesException() {
+ }
+ public OutOfResourcesException(String name) {
+ super(name);
+ }
+ }
+
+ /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
+
+ /**
+ * Surface creation flag: Surface is created hidden
+ */
+ public static final int HIDDEN = 0x00000004;
+
+ /**
+ * 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.
+ *
+ */
+ 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:
+ *
+ * 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.
+ *
+ */
+ public static final int NON_PREMULTIPLIED = 0x00000100;
+
+ /**
+ * 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.
+ *
+ */
+ public static final int OPAQUE = 0x00000400;
+
+ /**
+ * 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.
+ *
+ */
+ public static final int PROTECTED_APP = 0x00000800;
+
+ // 0x1000 is reserved for an independent DRM protected flag in framework
+
+ /**
+ * Surface creation flag: Creates a normal surface.
+ * This is the default.
+ *
+ */
+ public static final int FX_SURFACE_NORMAL = 0x00000000;
+
+ /**
+ * 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.
+ *
+ */
+ public static final int FX_SURFACE_DIM = 0x00020000;
+
+ /**
+ * Mask used for FX values above.
+ *
+ */
+ public static final int FX_SURFACE_MASK = 0x000F0000;
+
+ /* flags used with setFlags() (keep in sync with ISurfaceComposer.h) */
+
+ /**
+ * Surface flag: Hide the surface.
+ * Equivalent to calling hide().
+ */
+ public static final int SURFACE_HIDDEN = 0x01;
+
+
+ /* built-in physical display ids (keep in sync with ISurfaceComposer.h)
+ * these are different from the logical display ids used elsewhere in the framework */
+
+ /**
+ * Built-in physical display id: Main display.
+ * Use only with {@link SurfaceControl#getBuiltInDisplay()}.
+ */
+ public static final int BUILT_IN_DISPLAY_ID_MAIN = 0;
+
+ /**
+ * Built-in physical display id: Attached HDMI display.
+ * Use only with {@link SurfaceControl#getBuiltInDisplay()}.
+ */
+ public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
+
+
+
+ /**
+ * 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.
+ */
+ public SurfaceControl(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 ((flags & SurfaceControl.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());
+ }
+
+ checkHeadless();
+
+ mName = name;
+ mNativeObject = nativeCreate(session, name, w, h, format, flags);
+ if (mNativeObject == 0) {
+ throw new OutOfResourcesException(
+ "Couldn't allocate SurfaceControl native object");
+ }
+
+ mCloseGuard.open("release");
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Surface(name=" + mName + ")";
+ }
+
+ /**
+ * 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() {
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ mNativeObject = 0;
+ }
+ 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.
+ */
+ public void destroy() {
+ if (mNativeObject != 0) {
+ nativeDestroy(mNativeObject);
+ mNativeObject = 0;
+ }
+ mCloseGuard.close();
+ }
+
+ private void checkNotReleased() {
+ if (mNativeObject == 0) throw new NullPointerException(
+ "mNativeObject is null. Have you called release() already?");
+ }
+
+ /*
+ * set surface parameters.
+ * needs to be inside open/closeTransaction block
+ */
+
+ /** start a transaction */
+ public static void openTransaction() {
+ nativeOpenTransaction();
+ }
+
+ /** end a transaction */
+ public static void closeTransaction() {
+ nativeCloseTransaction();
+ }
+
+ /** flag the transaction as an animation */
+ public static void setAnimationTransaction() {
+ nativeSetAnimationTransaction();
+ }
+
+ public void setLayer(int zorder) {
+ checkNotReleased();
+ nativeSetLayer(mNativeObject, zorder);
+ }
+
+ public void setPosition(float x, float y) {
+ checkNotReleased();
+ nativeSetPosition(mNativeObject, x, y);
+ }
+
+ public void setSize(int w, int h) {
+ checkNotReleased();
+ nativeSetSize(mNativeObject, w, h);
+ }
+
+ public void hide() {
+ checkNotReleased();
+ nativeSetFlags(mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
+ }
+
+ public void show() {
+ checkNotReleased();
+ nativeSetFlags(mNativeObject, 0, SURFACE_HIDDEN);
+ }
+
+ public void setTransparentRegionHint(Region region) {
+ checkNotReleased();
+ nativeSetTransparentRegionHint(mNativeObject, region);
+ }
+
+ public void setAlpha(float alpha) {
+ checkNotReleased();
+ nativeSetAlpha(mNativeObject, alpha);
+ }
+
+ public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+ checkNotReleased();
+ nativeSetMatrix(mNativeObject, dsdx, dtdx, dsdy, dtdy);
+ }
+
+ public void setFlags(int flags, int mask) {
+ checkNotReleased();
+ nativeSetFlags(mNativeObject, flags, mask);
+ }
+
+ public void setWindowCrop(Rect crop) {
+ checkNotReleased();
+ if (crop != null) {
+ nativeSetWindowCrop(mNativeObject,
+ crop.left, crop.top, crop.right, crop.bottom);
+ } else {
+ nativeSetWindowCrop(mNativeObject, 0, 0, 0, 0);
+ }
+ }
+
+ public void setLayerStack(int layerStack) {
+ checkNotReleased();
+ nativeSetLayerStack(mNativeObject, layerStack);
+ }
+
+ /*
+ * set display parameters.
+ * needs to be inside open/closeTransaction block
+ */
+
+ /**
+ * Describes the properties of a physical display known to surface flinger.
+ */
+ public static final class PhysicalDisplayInfo {
+ public int width;
+ public int height;
+ public float refreshRate;
+ public float density;
+ public float xDpi;
+ public float yDpi;
+ public boolean secure;
+
+ public PhysicalDisplayInfo() {
+ }
+
+ public PhysicalDisplayInfo(PhysicalDisplayInfo other) {
+ copyFrom(other);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof PhysicalDisplayInfo && equals((PhysicalDisplayInfo)o);
+ }
+
+ public boolean equals(PhysicalDisplayInfo other) {
+ return other != null
+ && width == other.width
+ && height == other.height
+ && refreshRate == other.refreshRate
+ && density == other.density
+ && xDpi == other.xDpi
+ && yDpi == other.yDpi
+ && secure == other.secure;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0; // don't care
+ }
+
+ public void copyFrom(PhysicalDisplayInfo other) {
+ width = other.width;
+ height = other.height;
+ refreshRate = other.refreshRate;
+ density = other.density;
+ xDpi = other.xDpi;
+ yDpi = other.yDpi;
+ secure = other.secure;
+ }
+
+ // For debugging purposes
+ @Override
+ public String toString() {
+ return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, "
+ + "density " + density + ", " + xDpi + " x " + yDpi + " dpi, secure " + secure
+ + "}";
+ }
+ }
+
+ public static void unblankDisplay(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeUnblankDisplay(displayToken);
+ }
+
+ public static void blankDisplay(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeBlankDisplay(displayToken);
+ }
+
+ public static boolean getDisplayInfo(IBinder displayToken, SurfaceControl.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 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.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
+ displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
+ }
+
+ public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeSetDisplayLayerStack(displayToken, layerStack);
+ }
+
+ public static void setDisplaySurface(IBinder displayToken, Surface surface) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+
+ if (surface != null) {
+ synchronized (surface.mLock) {
+ nativeSetDisplaySurface(displayToken, surface.mNativeSurface);
+ }
+ } else {
+ nativeSetDisplaySurface(displayToken, 0);
+ }
+ }
+
+ public static IBinder createDisplay(String name, boolean secure) {
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+ return nativeCreateDisplay(name, secure);
+ }
+
+ public static IBinder getBuiltInDisplay(int builtInDisplayId) {
+ return nativeGetBuiltInDisplay(builtInDisplayId);
+ }
+
+
+ /**
+ * Copy the current screen contents into the provided {@link Surface}
+ *
+ * @param display The display to take the screenshot of.
+ * @param consumer The {@link Surface} to take the screenshot into.
+ * @param width The desired width of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param height The desired height of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param minLayer The lowest (bottom-most Z order) surface layer to
+ * include in the screenshot.
+ * @param maxLayer The highest (top-most Z order) surface layer to
+ * include in the screenshot.
+ */
+ public static void screenshot(IBinder display, Surface consumer,
+ int width, int height, int minLayer, int maxLayer) {
+ screenshot(display, consumer, width, height, minLayer, maxLayer, false);
+ }
+
+ /**
+ * Copy the current screen contents into the provided {@link Surface}
+ *
+ * @param display The display to take the screenshot of.
+ * @param consumer The {@link Surface} to take the screenshot into.
+ * @param width The desired width of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param height The desired height of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ */
+ public static void screenshot(IBinder display, Surface consumer,
+ int width, int height) {
+ screenshot(display, consumer, width, height, 0, 0, true);
+ }
+
+ /**
+ * Copy the current screen contents into the provided {@link Surface}
+ *
+ * @param display The display to take the screenshot of.
+ * @param consumer The {@link Surface} to take the screenshot into.
+ */
+ public static void screenshot(IBinder display, Surface consumer) {
+ screenshot(display, consumer, 0, 0, 0, 0, true);
+ }
+
+
+ /**
+ * Copy the current screen contents into a bitmap and return it.
+ *
+ * CAVEAT: Versions of screenshot that return a {@link Bitmap} can
+ * be extremely slow; avoid use unless absolutely necessary; prefer
+ * the versions that use a {@link Surface} instead, such as
+ * {@link SurfaceControl#screenshot(IBinder, Surface)}.
+ *
+ * @param width The desired width of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param height The desired height of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param minLayer The lowest (bottom-most Z order) surface layer to
+ * 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, or null
+ * if an error occurs. Make sure to call Bitmap.recycle() as soon as
+ * possible, once its content is not needed anymore.
+ */
+ public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer) {
+ // TODO: should take the display as a parameter
+ IBinder displayToken = SurfaceControl.getBuiltInDisplay(
+ SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
+ return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false);
+ }
+
+ /**
+ * Like {@link SurfaceControl#screenshot(int, int, int, int)} but includes all
+ * Surfaces in the screenshot.
+ *
+ * @param width The desired width of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param height The desired height of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @return Returns a Bitmap containing the screen contents, or null
+ * if an error occurs. Make sure to call Bitmap.recycle() as soon as
+ * possible, once its content is not needed anymore.
+ */
+ public static Bitmap screenshot(int width, int height) {
+ // TODO: should take the display as a parameter
+ IBinder displayToken = SurfaceControl.getBuiltInDisplay(
+ SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
+ return nativeScreenshot(displayToken, width, height, 0, 0, true);
+ }
+
+ private static void screenshot(IBinder display, Surface consumer,
+ int width, int height, int minLayer, int maxLayer, boolean allLayers) {
+ if (display == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (consumer == null) {
+ throw new IllegalArgumentException("consumer must not be null");
+ }
+ nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers);
+ }
+
+ private static void checkHeadless() {
+ if (HEADLESS) {
+ throw new UnsupportedOperationException("Device is headless");
+ }
+ }
+}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 9008521..793fb5e 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -103,6 +103,7 @@ public class SurfaceView extends View {
MyWindow mWindow;
final Rect mVisibleInsets = new Rect();
final Rect mWinFrame = new Rect();
+ final Rect mOverscanInsets = new Rect();
final Rect mContentInsets = new Rect();
final Configuration mConfiguration = new Configuration();
@@ -479,6 +480,7 @@ public class SurfaceView extends View {
if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) {
mLayout.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
}
+ mLayout.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
if (mWindow == null) {
Display display = getDisplay();
@@ -507,7 +509,7 @@ public class SurfaceView extends View {
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
visible ? VISIBLE : GONE,
WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
- mWinFrame, mContentInsets,
+ mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mConfiguration, mNewSurface);
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mReportDrawNeeded = true;
@@ -642,7 +644,7 @@ public class SurfaceView extends View {
}
@Override
- public void resized(Rect frame, Rect contentInsets,
+ public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
SurfaceView surfaceView = mSurfaceView.get();
if (surfaceView != null) {
@@ -753,12 +755,36 @@ public class SurfaceView extends View {
mHandler.sendMessage(msg);
}
+ /**
+ * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * The caller must redraw the entire surface.
+ * @return A canvas for drawing into the surface.
+ */
public Canvas lockCanvas() {
return internalLockCanvas(null);
}
- public Canvas lockCanvas(Rect dirty) {
- return internalLockCanvas(dirty);
+ /**
+ * Gets a {@link Canvas} for drawing into the SurfaceView's Surface
+ *
+ * After drawing into the provided {@link Canvas}, the caller must
+ * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
+ *
+ * @param inOutDirty 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 must redraw the entire dirty region as represented
+ * by the contents of the inOutDirty rectangle 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.
+ */
+ public Canvas lockCanvas(Rect inOutDirty) {
+ return internalLockCanvas(inOutDirty);
}
private final Canvas internalLockCanvas(Rect dirty) {
@@ -808,6 +834,12 @@ public class SurfaceView extends View {
return null;
}
+ /**
+ * 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) {
mSurface.unlockCanvasAndPost(canvas);
mSurfaceLock.unlock();
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 230f426..244dc33 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -108,6 +108,7 @@ public class TextureView extends View {
private HardwareLayer mLayer;
private SurfaceTexture mSurface;
private SurfaceTextureListener mListener;
+ private boolean mHadSurface;
private boolean mOpaque = true;
@@ -202,6 +203,11 @@ public class TextureView extends View {
Log.w(LOG_TAG, "A TextureView or a subclass can only be "
+ "used with hardware acceleration enabled.");
}
+
+ if (mHadSurface) {
+ invalidate(true);
+ mHadSurface = false;
+ }
}
@Override
@@ -241,6 +247,8 @@ public class TextureView extends View {
if (shouldRelease) mSurface.release();
mSurface = null;
mLayer = null;
+
+ mHadSurface = true;
}
}
@@ -260,7 +268,7 @@ public class TextureView extends View {
@Override
public void setLayerType(int layerType, Paint paint) {
if (paint != mLayerPaint) {
- mLayerPaint = paint;
+ mLayerPaint = paint == null ? new Paint() : paint;
invalidate();
}
}
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 82b3963..eb81f72 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -16,10 +16,7 @@
package android.view;
-import android.util.Poolable;
-import android.util.Pool;
-import android.util.Pools;
-import android.util.PoolableManager;
+import android.util.Pools.SynchronizedPool;
/**
* Helper for tracking the velocity of touch events, for implementing
@@ -31,30 +28,15 @@ import android.util.PoolableManager;
* {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)}
* and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id.
*/
-public final class VelocityTracker implements Poolable<VelocityTracker> {
- private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
- Pools.finitePool(new PoolableManager<VelocityTracker>() {
- public VelocityTracker newInstance() {
- return new VelocityTracker(null);
- }
-
- public void onAcquired(VelocityTracker element) {
- // Intentionally empty
- }
-
- public void onReleased(VelocityTracker element) {
- element.clear();
- }
- }, 2));
+public final class VelocityTracker {
+ private static final SynchronizedPool<VelocityTracker> sPool =
+ new SynchronizedPool<VelocityTracker>(2);
private static final int ACTIVE_POINTER_ID = -1;
private int mPtr;
private final String mStrategy;
- private VelocityTracker mNext;
- private boolean mIsPooled;
-
private static native int nativeInitialize(String strategy);
private static native void nativeDispose(int ptr);
private static native void nativeClear(int ptr);
@@ -73,7 +55,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @return Returns a new VelocityTracker.
*/
static public VelocityTracker obtain() {
- return sPool.acquire();
+ VelocityTracker instance = sPool.acquire();
+ return (instance != null) ? instance : new VelocityTracker(null);
}
/**
@@ -98,38 +81,11 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
*/
public void recycle() {
if (mStrategy == null) {
+ clear();
sPool.release(this);
}
}
- /**
- * @hide
- */
- public void setNextPoolable(VelocityTracker element) {
- mNext = element;
- }
-
- /**
- * @hide
- */
- public VelocityTracker getNextPoolable() {
- return mNext;
- }
-
- /**
- * @hide
- */
- public boolean isPooled() {
- return mIsPooled;
- }
-
- /**
- * @hide
- */
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
-
private VelocityTracker(String strategy) {
mPtr = nativeInitialize(strategy);
mStrategy = strategy;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4a3162f..01d80ac 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -40,6 +40,7 @@ import android.graphics.Shader;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManagerGlobal;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -52,10 +53,7 @@ import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
-import android.util.Pool;
-import android.util.Poolable;
-import android.util.PoolableManager;
-import android.util.Pools;
+import android.util.Pools.SynchronizedPool;
import android.util.Property;
import android.util.SparseArray;
import android.util.TypedValue;
@@ -691,6 +689,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public static final int NO_ID = -1;
+ private static boolean sUseBrokenMakeMeasureSpec = false;
+
/**
* This view does not want keystrokes. Use with TAKES_FOCUS_MASK when
* calling setFlags.
@@ -1561,9 +1561,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
int mAccessibilityViewId = NO_ID;
- /**
- * @hide
- */
private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
/**
@@ -1870,6 +1867,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final int LAYOUT_DIRECTION_DEFAULT = LAYOUT_DIRECTION_INHERIT;
/**
+ * Default horizontal layout direction.
+ * @hide
+ */
+ static final int LAYOUT_DIRECTION_RESOLVED_DEFAULT = LAYOUT_DIRECTION_LTR;
+
+ /**
* Indicates that the view is tracking some sort of transient state
* that the app should not need to be aware of, but that the framework
* should take special care to preserve.
@@ -1918,6 +1921,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT;
/**
+ * Default resolved text direction
+ * @hide
+ */
+ static final int TEXT_DIRECTION_RESOLVED_DEFAULT = TEXT_DIRECTION_FIRST_STRONG;
+
+ /**
* Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED)
* @hide
*/
@@ -1969,7 +1978,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
static final int PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT =
- TEXT_DIRECTION_FIRST_STRONG << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
+ TEXT_DIRECTION_RESOLVED_DEFAULT << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
/*
* Default text alignment. The text alignment of this View is inherited from its parent.
@@ -2028,6 +2037,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY;
/**
+ * Default resolved text alignment
+ * @hide
+ */
+ static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT = TEXT_ALIGNMENT_GRAVITY;
+
+ /**
* Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
* @hide
*/
@@ -2077,7 +2092,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Indicates whether if the view text alignment has been resolved to gravity
*/
private static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT =
- TEXT_ALIGNMENT_GRAVITY << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+ TEXT_ALIGNMENT_RESOLVED_DEFAULT << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
// Accessiblity constants for mPrivateFlags2
@@ -2515,8 +2530,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* The undefined cursor position.
+ *
+ * @hide
*/
- private static final int ACCESSIBILITY_CURSOR_POSITION_UNDEFINED = -1;
+ public static final int ACCESSIBILITY_CURSOR_POSITION_UNDEFINED = -1;
/**
* Indicates that the screen has changed state and is now off.
@@ -2741,6 +2758,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
TransformationInfo mTransformationInfo;
+ /**
+ * Current clip bounds. to which all drawing of this view are constrained.
+ */
+ private Rect mClipBounds = null;
+
private boolean mLastIsOpaque;
/**
@@ -2893,14 +2915,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @hide
*/
- int mUserPaddingLeftInitial = 0;
+ int mUserPaddingLeftInitial;
/**
* Cache initial right padding.
*
* @hide
*/
- int mUserPaddingRightInitial = 0;
+ int mUserPaddingRightInitial;
/**
* Default undefined padding
@@ -3206,6 +3228,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityDelegate mAccessibilityDelegate;
/**
+ * The view's overlay layer. Developers get a reference to the overlay via getOverlay()
+ * and add/remove objects to/from the overlay directly through the Overlay methods.
+ */
+ ViewOverlay mOverlay;
+
+ /**
* Consistency verifier for debugging purposes.
* @hide
*/
@@ -3237,6 +3265,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
mUserPaddingStart = UNDEFINED_PADDING;
mUserPaddingEnd = UNDEFINED_PADDING;
+
+ if (!sUseBrokenMakeMeasureSpec && context.getApplicationInfo().targetSdkVersion <=
+ Build.VERSION_CODES.JELLY_BEAN_MR1 ) {
+ // Older apps may need this compatibility hack for measurement.
+ sUseBrokenMakeMeasureSpec = true;
+ }
}
/**
@@ -3354,11 +3388,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
break;
case com.android.internal.R.styleable.View_paddingStart:
startPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING);
- startPaddingDefined = true;
+ startPaddingDefined = (startPadding != UNDEFINED_PADDING);
break;
case com.android.internal.R.styleable.View_paddingEnd:
endPadding = a.getDimensionPixelSize(attr, UNDEFINED_PADDING);
- endPaddingDefined = true;
+ endPaddingDefined = (endPadding != UNDEFINED_PADDING);
break;
case com.android.internal.R.styleable.View_scrollX:
x = a.getDimensionPixelOffset(attr, 0);
@@ -3678,10 +3712,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// 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) {
+ final boolean hasRelativePadding = startPaddingDefined || endPaddingDefined;
+
+ if (leftPaddingDefined && !hasRelativePadding) {
mUserPaddingLeftInitial = leftPadding;
}
- if (rightPaddingDefined) {
+ if (rightPaddingDefined && !hasRelativePadding) {
mUserPaddingRightInitial = rightPadding;
}
}
@@ -4370,10 +4406,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
mPrivateFlags |= PFLAG_FOCUSED;
+ View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
+
if (mParent != null) {
mParent.requestChildFocus(this, this);
}
+ if (mAttachInfo != null) {
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
+ }
+
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
@@ -4480,7 +4522,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
refreshDrawableState();
- ensureInputFocusOnFirstFocusable();
+ if (!rootViewRequestFocus()) {
+ notifyGlobalFocusCleared(this);
+ }
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
notifyAccessibilityStateChanged();
@@ -4488,11 +4532,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
- void ensureInputFocusOnFirstFocusable() {
+ void notifyGlobalFocusCleared(View oldFocus) {
+ if (oldFocus != null && mAttachInfo != null) {
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null);
+ }
+ }
+
+ boolean rootViewRequestFocus() {
View root = getRootView();
if (root != null) {
- root.requestFocus();
+ return root.requestFocus();
}
+ return false;
}
/**
@@ -4838,13 +4889,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
event.setEnabled(isEnabled());
event.setContentDescription(mContentDescription);
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && mAttachInfo != null) {
- ArrayList<View> focusablesTempList = mAttachInfo.mTempArrayList;
- getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD,
- FOCUSABLES_ALL);
- event.setItemCount(focusablesTempList.size());
- event.setCurrentItemIndex(focusablesTempList.indexOf(this));
- focusablesTempList.clear();
+ switch (event.getEventType()) {
+ case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
+ ArrayList<View> focusablesTempList = (mAttachInfo != null)
+ ? mAttachInfo.mTempArrayList : new ArrayList<View>();
+ getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD, FOCUSABLES_ALL);
+ event.setItemCount(focusablesTempList.size());
+ event.setCurrentItemIndex(focusablesTempList.indexOf(this));
+ if (mAttachInfo != null) {
+ focusablesTempList.clear();
+ }
+ } break;
+ case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text != null && text.length() > 0) {
+ event.setFromIndex(getAccessibilitySelectionStart());
+ event.setToIndex(getAccessibilitySelectionEnd());
+ event.setItemCount(text.length());
+ }
+ } break;
}
}
@@ -4865,6 +4928,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see AccessibilityNodeInfo
*/
public AccessibilityNodeInfo createAccessibilityNodeInfo() {
+ if (mAccessibilityDelegate != null) {
+ return mAccessibilityDelegate.createAccessibilityNodeInfo(this);
+ } else {
+ return createAccessibilityNodeInfoInternal();
+ }
+ }
+
+ /**
+ * @see #createAccessibilityNodeInfo()
+ */
+ AccessibilityNodeInfo createAccessibilityNodeInfoInternal() {
AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
if (provider != null) {
return provider.createAccessibilityNodeInfo(View.NO_ID);
@@ -4986,6 +5060,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (label != null) {
info.setLabeledBy(label);
}
+
+ if ((mAttachInfo.mAccessibilityFetchFlags
+ & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0
+ && Resources.resourceHasPackage(mID)) {
+ try {
+ String viewId = getResources().getResourceName(mID);
+ info.setViewIdResourceName(viewId);
+ } catch (Resources.NotFoundException nfe) {
+ /* ignore */
+ }
+ }
}
if (mLabelForId != View.NO_ID) {
@@ -5041,7 +5126,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
}
- if (mContentDescription != null && mContentDescription.length() > 0) {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text != null && text.length() > 0) {
+ info.setTextSelection(getAccessibilitySelectionStart(), getAccessibilitySelectionEnd());
+
+ info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
@@ -5615,20 +5704,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
mUserPaddingStart = UNDEFINED_PADDING;
mUserPaddingEnd = UNDEFINED_PADDING;
- if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
- || mAttachInfo == null
- || (mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0) {
- internalSetPadding(insets.left, insets.top, insets.right, insets.bottom);
- return true;
- } else {
- internalSetPadding(0, 0, 0, 0);
- return false;
+ Rect localInsets = sThreadLocal.get();
+ if (localInsets == null) {
+ localInsets = new Rect();
+ sThreadLocal.set(localInsets);
}
+ boolean res = computeFitSystemWindows(insets, localInsets);
+ internalSetPadding(localInsets.left, localInsets.top,
+ localInsets.right, localInsets.bottom);
+ return res;
}
return false;
}
/**
+ * @hide Compute the insets that should be consumed by this view and the ones
+ * that should propagate to those under it.
+ */
+ protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
+ if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
+ || mAttachInfo == null
+ || ((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0
+ && !mAttachInfo.mOverscanRequested)) {
+ outLocalInsets.set(inoutInsets);
+ inoutInsets.set(0, 0, 0, 0);
+ return true;
+ } else {
+ // The application wants to take care of fitting system window for
+ // the content... however we still need to take care of any overscan here.
+ final Rect overscan = mAttachInfo.mOverscanInsets;
+ outLocalInsets.set(overscan);
+ inoutInsets.left -= overscan.left;
+ inoutInsets.top -= overscan.top;
+ inoutInsets.right -= overscan.right;
+ inoutInsets.bottom -= overscan.bottom;
+ return false;
+ }
+ }
+
+ /**
* Sets whether or not this view should account for system screen decorations
* such as the status bar and inset its content; that is, controlling whether
* the default implementation of {@link #fitSystemWindows(Rect)} will be
@@ -5923,7 +6037,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
if (targetSdkVersion < JELLY_BEAN_MR1) {
mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
- return LAYOUT_DIRECTION_LTR;
+ return LAYOUT_DIRECTION_RESOLVED_DEFAULT;
}
return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ==
PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
@@ -5979,8 +6093,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mTransientStateCount = 0;
Log.e(VIEW_LOG_TAG, "hasTransientState decremented below 0: " +
"unmatched pair of setHasTransientState calls");
- }
- if ((hasTransientState && mTransientStateCount == 1) ||
+ } else if ((hasTransientState && mTransientStateCount == 1) ||
(!hasTransientState && mTransientStateCount == 0)) {
// update flag if we've just incremented up from 0 or decremented down to 0
mPrivateFlags2 = (mPrivateFlags2 & ~PFLAG2_HAS_TRANSIENT_STATE) |
@@ -6515,12 +6628,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
public void clearAccessibilityFocus() {
- if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) {
- mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED;
- invalidate();
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
- notifyAccessibilityStateChanged();
- }
+ clearAccessibilityFocusNoCallbacks();
// Clear the global reference of accessibility focus if this
// view or any of its descendants had accessibility focus.
ViewRootImpl viewRootImpl = getViewRootImpl();
@@ -6567,6 +6675,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) {
mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED;
invalidate();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+ notifyAccessibilityStateChanged();
}
}
@@ -6817,7 +6927,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public boolean includeForAccessibility() {
if (mAttachInfo != null) {
- return mAttachInfo.mIncludeNotImportantViews || isImportantForAccessibility();
+ return (mAttachInfo.mAccessibilityFetchFlags
+ & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
+ || isImportantForAccessibility();
}
return false;
}
@@ -6966,44 +7078,44 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (arguments != null) {
final int granularity = arguments.getInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
- return nextAtGranularity(granularity);
+ final boolean extendSelection = arguments.getBoolean(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
+ return traverseAtGranularity(granularity, true, extendSelection);
}
} break;
case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
if (arguments != null) {
final int granularity = arguments.getInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
- return previousAtGranularity(granularity);
+ final boolean extendSelection = arguments.getBoolean(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
+ return traverseAtGranularity(granularity, false, extendSelection);
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text == null) {
+ return false;
+ }
+ final int start = (arguments != null) ? arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
+ final int end = (arguments != null) ? arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
+ // Only cursor position can be specified (selection length == 0)
+ if ((getAccessibilitySelectionStart() != start
+ || getAccessibilitySelectionEnd() != end)
+ && (start == end)) {
+ setAccessibilitySelection(start, end);
+ notifyAccessibilityStateChanged();
+ return true;
}
} break;
}
return false;
}
- private boolean nextAtGranularity(int granularity) {
- CharSequence text = getIterableTextForAccessibility();
- if (text == null || text.length() == 0) {
- return false;
- }
- TextSegmentIterator iterator = getIteratorForGranularity(granularity);
- if (iterator == null) {
- return false;
- }
- final int current = getAccessibilityCursorPosition();
- final int[] range = iterator.following(current);
- if (range == null) {
- return false;
- }
- final int start = range[0];
- final int end = range[1];
- setAccessibilityCursorPosition(end);
- sendViewTextTraversedAtGranularityEvent(
- AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
- granularity, start, end);
- return true;
- }
-
- private boolean previousAtGranularity(int granularity) {
+ private boolean traverseAtGranularity(int granularity, boolean forward,
+ boolean extendSelection) {
CharSequence text = getIterableTextForAccessibility();
if (text == null || text.length() == 0) {
return false;
@@ -7012,31 +7124,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (iterator == null) {
return false;
}
- int current = getAccessibilityCursorPosition();
+ int current = getAccessibilitySelectionEnd();
if (current == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
- current = text.length();
- setAccessibilityCursorPosition(current);
- } else if (granularity == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER) {
- // When traversing by character we always put the cursor after the character
- // to ease edit and have to compensate before asking the for previous segment.
- current--;
- setAccessibilityCursorPosition(current);
- }
- final int[] range = iterator.preceding(current);
+ current = forward ? 0 : text.length();
+ }
+ final int[] range = forward ? iterator.following(current) : iterator.preceding(current);
if (range == null) {
return false;
}
- final int start = range[0];
- final int end = range[1];
- // Always put the cursor after the character to ease edit.
- if (granularity == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER) {
- setAccessibilityCursorPosition(end);
+ final int segmentStart = range[0];
+ final int segmentEnd = range[1];
+ int selectionStart;
+ int selectionEnd;
+ if (extendSelection && isAccessibilitySelectionExtendable()) {
+ selectionStart = getAccessibilitySelectionStart();
+ if (selectionStart == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
+ selectionStart = forward ? segmentStart : segmentEnd;
+ }
+ selectionEnd = forward ? segmentEnd : segmentStart;
} else {
- setAccessibilityCursorPosition(start);
+ selectionStart = selectionEnd= forward ? segmentEnd : segmentStart;
}
- sendViewTextTraversedAtGranularityEvent(
- AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
- granularity, start, end);
+ setAccessibilitySelection(selectionStart, selectionEnd);
+ final int action = forward ? AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ : AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY;
+ sendViewTextTraversedAtGranularityEvent(action, granularity, segmentStart, segmentEnd);
return true;
}
@@ -7052,17 +7164,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Gets whether accessibility selection can be extended.
+ *
+ * @return If selection is extensible.
+ *
+ * @hide
+ */
+ public boolean isAccessibilitySelectionExtendable() {
+ return false;
+ }
+
+ /**
* @hide
*/
- public int getAccessibilityCursorPosition() {
+ public int getAccessibilitySelectionStart() {
return mAccessibilityCursorPosition;
}
/**
* @hide
*/
- public void setAccessibilityCursorPosition(int position) {
- mAccessibilityCursorPosition = position;
+ public int getAccessibilitySelectionEnd() {
+ return getAccessibilitySelectionStart();
+ }
+
+ /**
+ * @hide
+ */
+ public void setAccessibilitySelection(int start, int end) {
+ if (start == end && end == mAccessibilityCursorPosition) {
+ return;
+ }
+ if (start >= 0 && start == end && end <= getIterableTextForAccessibility().length()) {
+ mAccessibilityCursorPosition = start;
+ } else {
+ mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
+ }
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
}
private void sendViewTextTraversedAtGranularityEvent(int action, int granularity,
@@ -7965,13 +8103,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* delivered to the focused view.
* </p>
* <pre> public boolean onGenericMotionEvent(MotionEvent event) {
- * if ((event.getSource() &amp; InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+ * if (event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) {
* if (event.getAction() == MotionEvent.ACTION_MOVE) {
* // process the joystick movement...
* return true;
* }
* }
- * if ((event.getSource() &amp; InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ * if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) {
* switch (event.getAction()) {
* case MotionEvent.ACTION_HOVER_MOVE:
* // process the mouse hover movement...
@@ -8071,7 +8209,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// in onHoverEvent.
// Note that onGenericMotionEvent will be called by default when
// onHoverEvent returns false (refer to dispatchGenericMotionEvent).
- return dispatchGenericMotionEventInternal(event);
+ dispatchGenericMotionEventInternal(event);
+ // The event was already handled by calling setHovered(), so always
+ // return true.
+ return true;
}
return false;
@@ -8536,7 +8677,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Change the view's z order in the tree, so it's on top of other sibling
- * views
+ * views. This ordering change may affect layout, if the parent container
+ * uses an order-dependent layout scheme (e.g., LinearLayout). This
+ * method should be followed by calls to {@link #requestLayout()} and
+ * {@link View#invalidate()} on the parent.
+ *
+ * @see ViewGroup#bringChildToFront(View)
*/
public void bringToFront() {
if (mParent != null) {
@@ -9332,17 +9478,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>Sets the opacity of the view. This is a value from 0 to 1, where 0 means the view is
* completely transparent and 1 means the view is completely opaque.</p>
*
+ * <p> Note that setting alpha to a translucent value (0 < alpha < 1) can have significant
+ * performance implications, especially for large views. It is best to use the alpha property
+ * sparingly and transiently, as in the case of fading animations.</p>
+ *
+ * <p>For a view with a frequently changing alpha, such as during a fading animation, it is
+ * strongly recommended for performance reasons to either override
+ * {@link #hasOverlappingRendering()} to return false if appropriate, or setting a
+ * {@link #setLayerType(int, android.graphics.Paint) layer type} on the view.</p>
+ *
* <p>If this view overrides {@link #onSetAlpha(int)} to return true, then this view is
- * responsible for applying the opacity itself. Otherwise, calling this method is
- * equivalent to calling {@link #setLayerType(int, android.graphics.Paint)} and
- * setting a hardware layer.</p>
+ * responsible for applying the opacity itself.</p>
*
- * <p>Note that setting alpha to a translucent value (0 < alpha < 1) may have
- * performance implications. It is generally best to use the alpha property sparingly and
- * transiently, as in the case of fading animations.</p>
+ * <p>Note that if the view is backed by a
+ * {@link #setLayerType(int, android.graphics.Paint) layer} and is associated with a
+ * {@link #setLayerPaint(android.graphics.Paint) layer paint}, setting an alpha value less than
+ * 1.0 will supercede the alpha of the layer paint.</p>
*
* @param alpha The opacity of the view.
*
+ * @see #hasOverlappingRendering()
* @see #setLayerType(int, android.graphics.Paint)
*
* @attr ref android.R.styleable#View_alpha
@@ -9442,7 +9597,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mDisplayList.setTop(mTop);
}
- onSizeChanged(width, mBottom - mTop, width, oldHeight);
+ sizeChange(width, mBottom - mTop, width, oldHeight);
if (!matrixIsIdentity) {
if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
@@ -9515,7 +9670,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mDisplayList.setBottom(mBottom);
}
- onSizeChanged(width, mBottom - mTop, width, oldHeight);
+ sizeChange(width, mBottom - mTop, width, oldHeight);
if (!matrixIsIdentity) {
if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
@@ -9582,7 +9737,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mDisplayList.setLeft(left);
}
- onSizeChanged(mRight - mLeft, height, oldWidth, height);
+ sizeChange(mRight - mLeft, height, oldWidth, height);
if (!matrixIsIdentity) {
if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
@@ -9646,7 +9801,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mDisplayList.setRight(mRight);
}
- onSizeChanged(mRight - mLeft, height, oldWidth, height);
+ sizeChange(mRight - mLeft, height, oldWidth, height);
if (!matrixIsIdentity) {
if ((mPrivateFlags & PFLAG_PIVOT_EXPLICITLY_SET) == 0) {
@@ -9806,8 +9961,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
outRect.set(mLeft, mTop, mRight, mBottom);
} else {
final RectF tmpRect = mAttachInfo.mTmpTransformRect;
- tmpRect.set(-info.mPivotX, -info.mPivotY,
- getWidth() - info.mPivotX, getHeight() - info.mPivotY);
+ tmpRect.set(0, 0, getWidth(), getHeight());
info.mMatrix.mapRect(tmpRect);
outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,
(int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);
@@ -9928,7 +10082,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mTop += offset;
mBottom += offset;
if (mDisplayList != null) {
- mDisplayList.offsetTopBottom(offset);
+ mDisplayList.offsetTopAndBottom(offset);
invalidateViewProperty(false, false);
} else {
if (!matrixIsIdentity) {
@@ -9942,7 +10096,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Offset this view's horizontal location by the specified amount of pixels.
*
- * @param offset the numer of pixels to offset the view by
+ * @param offset the number of pixels to offset the view by
*/
public void offsetLeftAndRight(int offset) {
if (offset != 0) {
@@ -9976,7 +10130,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mLeft += offset;
mRight += offset;
if (mDisplayList != null) {
- mDisplayList.offsetLeftRight(offset);
+ mDisplayList.offsetLeftAndRight(offset);
invalidateViewProperty(false, false);
} else {
if (!matrixIsIdentity) {
@@ -10435,10 +10589,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
RectF boundingRect = mAttachInfo.mTmpTransformRect;
boundingRect.set(rect);
getMatrix().mapRect(boundingRect);
- rect.set((int) (boundingRect.left - 0.5f),
- (int) (boundingRect.top - 0.5f),
- (int) (boundingRect.right + 0.5f),
- (int) (boundingRect.bottom + 0.5f));
+ rect.set((int) Math.floor(boundingRect.left),
+ (int) Math.floor(boundingRect.top),
+ (int) Math.ceil(boundingRect.right),
+ (int) Math.ceil(boundingRect.bottom));
}
}
@@ -10496,7 +10650,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// Opaque if:
// - Has a background
// - Background is opaque
- // - Doesn't have scrollbars or scrollbars are inside overlay
+ // - Doesn't have scrollbars or scrollbars overlay
if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
@@ -10506,7 +10660,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final int flags = mViewFlags;
if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
- (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY) {
+ (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||
+ (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {
mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
} else {
mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;
@@ -10749,7 +10904,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
- final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire();
+ final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.obtain();
info.target = this;
info.left = left;
info.top = top;
@@ -10798,7 +10953,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
- final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire();
+ final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.obtain();
info.target = this;
info.left = left;
info.top = top;
@@ -11406,7 +11561,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final int scrollY = mScrollY;
final int inside = (viewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
- int left, top, right, bottom;
+ int left;
+ int top;
+ int right;
+ int bottom;
if (drawHorizontalScrollBar) {
int size = scrollBar.getSize(false);
@@ -11564,18 +11722,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
imm.focusIn(this);
}
- if (mAttachInfo != null && mDisplayList != null) {
- mAttachInfo.mViewRootImpl.dequeueDisplayList(mDisplayList);
+ if (mDisplayList != null) {
+ mDisplayList.clearDirty();
}
}
/**
* Resolve all RTL related properties.
*
+ * @return true if resolution of RTL properties has been done
+ *
* @hide
*/
- public void resolveRtlPropertiesIfNeeded() {
- if (!needRtlPropertiesResolution()) return;
+ public boolean resolveRtlPropertiesIfNeeded() {
+ if (!needRtlPropertiesResolution()) return false;
// Order is important here: LayoutDirection MUST be resolved first
if (!isLayoutDirectionResolved()) {
@@ -11596,6 +11756,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
resolveDrawables();
}
onRtlPropertiesChanged(getLayoutDirection());
+ return true;
}
/**
@@ -11648,6 +11809,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* @return true if RTL properties need resolution.
+ *
*/
private boolean needRtlPropertiesResolution() {
return (mPrivateFlags2 & ALL_RTL_PROPERTIES_RESOLVED) != ALL_RTL_PROPERTIES_RESOLVED;
@@ -11691,11 +11853,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// later to get the correct resolved value
if (!canResolveLayoutDirection()) return false;
- View parent = ((View) mParent);
// Parent has not yet resolved, LTR is still the default
- if (!parent.isLayoutDirectionResolved()) return false;
+ if (!mParent.isLayoutDirectionResolved()) return false;
- if (parent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ if (mParent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
}
break;
@@ -11728,8 +11889,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public boolean canResolveLayoutDirection() {
switch (getRawLayoutDirection()) {
case LAYOUT_DIRECTION_INHERIT:
- return (mParent != null) && (mParent instanceof ViewGroup) &&
- ((ViewGroup) mParent).canResolveLayoutDirection();
+ return (mParent != null) && mParent.canResolveLayoutDirection();
default:
return true;
}
@@ -11757,8 +11917,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* @return true if layout direction has been resolved.
+ * @hide
*/
- private boolean isLayoutDirectionResolved() {
+ public boolean isLayoutDirectionResolved() {
return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED) == PFLAG2_LAYOUT_DIRECTION_RESOLVED;
}
@@ -11783,26 +11944,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// 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;
+ } else {
+ mUserPaddingRight = mUserPaddingRightInitial;
}
if (mUserPaddingEnd != UNDEFINED_PADDING) {
mUserPaddingLeft = mUserPaddingEnd;
+ } else {
+ mUserPaddingLeft = mUserPaddingLeftInitial;
}
break;
case LAYOUT_DIRECTION_LTR:
default:
if (mUserPaddingStart != UNDEFINED_PADDING) {
mUserPaddingLeft = mUserPaddingStart;
+ } else {
+ mUserPaddingLeft = mUserPaddingLeftInitial;
}
if (mUserPaddingEnd != UNDEFINED_PADDING) {
mUserPaddingRight = mUserPaddingEnd;
+ } else {
+ mUserPaddingRight = mUserPaddingRightInitial;
}
}
@@ -11845,6 +12010,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mAttachInfo != null) {
if (mDisplayList != null) {
+ mDisplayList.markDirty();
mAttachInfo.mViewRootImpl.enqueueDisplayList(mDisplayList);
}
mAttachInfo.mViewRootImpl.cancelInvalidate(this);
@@ -11875,6 +12041,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Retrieve the {@link WindowId} for the window this view is
+ * currently attached to.
+ */
+ public WindowId getWindowId() {
+ if (mAttachInfo == null) {
+ return null;
+ }
+ if (mAttachInfo.mWindowId == null) {
+ try {
+ mAttachInfo.mIWindowId = mAttachInfo.mSession.getWindowId(
+ mAttachInfo.mWindowToken);
+ mAttachInfo.mWindowId = new WindowId(
+ mAttachInfo.mIWindowId);
+ } catch (RemoteException e) {
+ }
+ }
+ return mAttachInfo.mWindowId;
+ }
+
+ /**
* Retrieve a unique token identifying the top-level "real" window of
* the window that this view is attached to. That is, this is like
* {@link #getWindowToken}, except if the window this view in is a panel
@@ -11921,6 +12107,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
//System.out.println("Attached! " + this);
mAttachInfo = info;
+ if (mOverlay != null) {
+ mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
+ }
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
@@ -11989,6 +12178,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
mAttachInfo = null;
+ if (mOverlay != null) {
+ mOverlay.getOverlayView().dispatchDetachedFromWindow();
+ }
}
/**
@@ -12170,8 +12362,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* <p>Specifies the type of layer backing this view. The layer can be
- * {@link #LAYER_TYPE_NONE disabled}, {@link #LAYER_TYPE_SOFTWARE software} or
- * {@link #LAYER_TYPE_HARDWARE hardware}.</p>
+ * {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
+ * {@link #LAYER_TYPE_HARDWARE}.</p>
*
* <p>A layer is associated with an optional {@link android.graphics.Paint}
* instance that controls how the layer is composed on screen. The following
@@ -12183,13 +12375,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* </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>
+ * {@link #setAlpha(float)}, the alpha value of the layer's paint is superceded
+ * by this view's 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}
+ * <p>Refer to the documentation of {@link #LAYER_TYPE_NONE},
+ * {@link #LAYER_TYPE_SOFTWARE} and {@link #LAYER_TYPE_HARDWARE}
* for more information on when and how to use layers.</p>
*
* @param layerType The type of layer to use with this view, must be one of
@@ -12259,11 +12449,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <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>
+ * <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 superceded by this view's 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
@@ -12548,8 +12735,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * @return The HardwareRenderer associated with that view or null if hardware rendering
- * is not supported or this this has not been attached to a window.
+ * @return The {@link HardwareRenderer} associated with that view or null if
+ * hardware rendering is not supported or this view is not attached
+ * to a window.
*
* @hide
*/
@@ -12604,15 +12792,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
boolean caching = false;
- final HardwareCanvas canvas = displayList.start();
int width = mRight - mLeft;
int height = mBottom - mTop;
+ int layerType = getLayerType();
+
+ final HardwareCanvas canvas = displayList.start(width, height);
try {
- canvas.setViewport(width, height);
- // The dirty rect should always be null for a display list
- canvas.onPreDraw(null);
- int layerType = getLayerType();
if (!isLayer && layerType != LAYER_TYPE_NONE) {
if (layerType == LAYER_TYPE_HARDWARE) {
final HardwareLayer layer = getHardwareLayer();
@@ -12645,13 +12831,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
+ if (mOverlay != null && !mOverlay.isEmpty()) {
+ mOverlay.getOverlayView().draw(canvas);
+ }
} else {
draw(canvas);
}
}
} finally {
- canvas.onPostDraw();
-
displayList.end();
displayList.setCaching(caching);
if (isLayer) {
@@ -12696,7 +12883,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private void clearDisplayList() {
if (mDisplayList != null) {
- mDisplayList.invalidate();
mDisplayList.clear();
}
}
@@ -12961,6 +13147,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
+ if (mOverlay != null && !mOverlay.isEmpty()) {
+ mOverlay.getOverlayView().draw(canvas);
+ }
} else {
draw(canvas);
}
@@ -13186,6 +13375,47 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Sets a rectangular area on this view to which the view will be clipped
+ * when it is drawn. Setting the value to null will remove the clip bounds
+ * and the view will draw normally, using its full bounds.
+ *
+ * @param clipBounds The rectangular area, in the local coordinates of
+ * this view, to which future drawing operations will be clipped.
+ */
+ public void setClipBounds(Rect clipBounds) {
+ if (clipBounds != null) {
+ if (clipBounds.equals(mClipBounds)) {
+ return;
+ }
+ if (mClipBounds == null) {
+ invalidate();
+ mClipBounds = new Rect(clipBounds);
+ } else {
+ invalidate(Math.min(mClipBounds.left, clipBounds.left),
+ Math.min(mClipBounds.top, clipBounds.top),
+ Math.max(mClipBounds.right, clipBounds.right),
+ Math.max(mClipBounds.bottom, clipBounds.bottom));
+ mClipBounds.set(clipBounds);
+ }
+ } else {
+ if (mClipBounds != null) {
+ invalidate();
+ mClipBounds = null;
+ }
+ }
+ }
+
+ /**
+ * Returns a copy of the current {@link #setClipBounds(Rect) clipBounds}.
+ *
+ * @return A copy of the current clip bounds if clip bounds are set,
+ * otherwise null.
+ */
+ public Rect getClipBounds() {
+ return (mClipBounds != null) ? new Rect(mClipBounds) : null;
+ }
+
+ /**
* Utility function, called by draw(canvas, parent, drawingTime) to handle the less common
* case of an active Animation being run on the view.
*/
@@ -13255,8 +13485,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
displayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
displayList.setHasOverlappingRendering(hasOverlappingRendering());
if (mParent instanceof ViewGroup) {
- displayList.setClipChildren(
- (((ViewGroup)mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0);
+ displayList.setClipToBounds(
+ (((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0);
}
float alpha = 1;
if (mParent instanceof ViewGroup && (((ViewGroup) mParent).mGroupFlags &
@@ -13272,7 +13502,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
alpha = transform.getAlpha();
}
if ((transformType & Transformation.TYPE_MATRIX) != 0) {
- displayList.setStaticMatrix(transform.getMatrix());
+ displayList.setMatrix(transform.getMatrix());
}
}
}
@@ -13347,8 +13577,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
transformToApply = parent.mChildTransformation;
} else {
- if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) == PFLAG3_VIEW_IS_ANIMATING_TRANSFORM &&
- mDisplayList != null) {
+ 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 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
@@ -13545,7 +13775,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) == ViewGroup.FLAG_CLIP_CHILDREN &&
- !useDisplayListProperties) {
+ !useDisplayListProperties && cache == null) {
if (offsetForScroll) {
canvas.clipRect(sx, sy, sx + (mRight - mLeft), sy + (mBottom - mTop));
} else {
@@ -13661,6 +13891,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param canvas The Canvas to which the View is rendered.
*/
public void draw(Canvas canvas) {
+ if (mClipBounds != null) {
+ canvas.clipRect(mClipBounds);
+ }
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
@@ -13716,6 +13949,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
+ if (mOverlay != null && !mOverlay.isEmpty()) {
+ mOverlay.getOverlayView().dispatchDraw(canvas);
+ }
+
// we're done...
return;
}
@@ -13855,6 +14092,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
+
+ if (mOverlay != null && !mOverlay.isEmpty()) {
+ mOverlay.getOverlayView().dispatchDraw(canvas);
+ }
+ }
+
+ /**
+ * Returns the overlay for this view, creating it if it does not yet exist.
+ * Adding drawables to the overlay will cause them to be displayed whenever
+ * the view itself is redrawn. Objects in the overlay should be actively
+ * managed: remove them when they should not be displayed anymore. The
+ * overlay will always have the same size as its host view.
+ *
+ * <p>Note: Overlays do not currently work correctly with {@link
+ * SurfaceView} or {@link TextureView}; contents in overlays for these
+ * types of views may not display correctly.</p>
+ *
+ * @return The ViewOverlay object for this view.
+ * @see ViewOverlay
+ */
+ public ViewOverlay getOverlay() {
+ if (mOverlay == null) {
+ mOverlay = new ViewOverlay(mContext, this);
+ }
+ return mOverlay;
}
/**
@@ -13977,6 +14239,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Return true if o is a ViewGroup that is laying out using optical bounds.
+ * @hide
+ */
+ public static boolean isLayoutModeOptical(Object o) {
+ return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
+ }
+
+ private boolean setOpticalFrame(int left, int top, int right, int bottom) {
+ Insets parentInsets = mParent instanceof View ?
+ ((View) mParent).getOpticalInsets() : Insets.NONE;
+ Insets childInsets = getOpticalInsets();
+ return setFrame(
+ left + parentInsets.left - childInsets.left,
+ top + parentInsets.top - childInsets.top,
+ right + parentInsets.left + childInsets.right,
+ bottom + parentInsets.top + childInsets.bottom);
+ }
+
+ /**
* Assign a size and position to a view and all of its
* descendants
*
@@ -14002,7 +14283,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
- boolean changed = setFrame(l, t, r, b);
+ boolean changed = isLayoutModeOptical(mParent) ?
+ setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
@@ -14090,7 +14372,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mTransformationInfo.mMatrixDirty = true;
}
}
- onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
+ sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
@@ -14114,6 +14396,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return changed;
}
+ private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
+ onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
+ if (mOverlay != null) {
+ mOverlay.getOverlayView().setRight(newWidth);
+ mOverlay.getOverlayView().setBottom(newHeight);
+ }
+ }
+
/**
* Finalize inflating a view from XML. This is called as the last phase
* of inflation, after all child views have been added.
@@ -14444,6 +14734,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mBackground instanceof ColorDrawable) {
((ColorDrawable) mBackground.mutate()).setColor(color);
computeOpaqueFlags();
+ mBackgroundResource = 0;
} else {
setBackground(new ColorDrawable(color));
}
@@ -14818,6 +15109,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return (mUserPaddingStart != UNDEFINED_PADDING || mUserPaddingEnd != UNDEFINED_PADDING);
}
+ Insets computeOpticalInsets() {
+ return (mBackground == null) ? Insets.NONE : mBackground.getOpticalInsets();
+ }
+
/**
* @hide
*/
@@ -14841,19 +15136,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public Insets getOpticalInsets() {
if (mLayoutInsets == null) {
- mLayoutInsets = (mBackground == null) ? Insets.NONE : mBackground.getLayoutInsets();
+ mLayoutInsets = computeOpticalInsets();
}
return mLayoutInsets;
}
/**
- * @hide
- */
- public void setLayoutInsets(Insets layoutInsets) {
- mLayoutInsets = layoutInsets;
- }
-
- /**
* Changes the selection state of this view. A view can be selected or not.
* Note that selection is not the same as focus. Views are typically
* selected in the context of an AdapterView like ListView or GridView;
@@ -15128,8 +15416,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @param accessibilityId The accessibility id.
* @return The found view.
+ *
+ * @hide
*/
- View findViewByAccessibilityIdTraversal(int accessibilityId) {
+ public View findViewByAccessibilityIdTraversal(int accessibilityId) {
if (getAccessibilityViewId() == accessibilityId) {
return this;
}
@@ -15460,17 +15750,50 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Returns whether the view hierarchy is currently undergoing a layout pass. This
+ * information is useful to avoid situations such as calling {@link #requestLayout()} during
+ * a layout pass.
+ *
+ * @return whether the view hierarchy is currently undergoing a layout pass
+ */
+ public boolean isInLayout() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ return (viewRoot != null && viewRoot.isInLayout());
+ }
+
+ /**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
- * tree.
+ * tree. This should not be called while the view hierarchy is currently in a layout
+ * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
+ * end of the current layout pass (and then layout will run again) or after the current
+ * frame is drawn and the next layout occurs.
+ *
+ * <p>Subclasses which override this method should call the superclass method to
+ * handle possible request-during-layout errors correctly.</p>
*/
public void requestLayout() {
+ if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
+ // Only trigger request-during-layout logic if this is the view requesting it,
+ // not the views in its parent hierarchy
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null && viewRoot.isInLayout()) {
+ if (!viewRoot.requestLayoutDuringLayout(this)) {
+ return;
+ }
+ }
+ mAttachInfo.mViewRequestingLayout = this;
+ }
+
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
+ if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
+ mAttachInfo.mViewRequestingLayout = null;
+ }
}
/**
@@ -15504,6 +15827,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
+ boolean optical = isLayoutModeOptical(this);
+ if (optical != isLayoutModeOptical(mParent)) {
+ Insets insets = getOpticalInsets();
+ int oWidth = insets.left + insets.right;
+ int oHeight = insets.top + insets.bottom;
+ widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
+ heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
+ }
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
@@ -15583,7 +15914,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * <p>This mehod must be called by {@link #onMeasure(int, int)} to store the
+ * <p>This method must be called by {@link #onMeasure(int, int)} to store the
* measured width and measured height. Failing to do so will trigger an
* exception at measurement time.</p>
*
@@ -15595,6 +15926,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
+ boolean optical = isLayoutModeOptical(this);
+ if (optical != isLayoutModeOptical(mParent)) {
+ Insets insets = getOpticalInsets();
+ int opticalWidth = insets.left + insets.right;
+ int opticalHeight = insets.top + insets.bottom;
+
+ measuredWidth += optical ? opticalWidth : -opticalWidth;
+ measuredHeight += optical ? opticalHeight : -opticalHeight;
+ }
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
@@ -16695,6 +17035,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @attr ref android.R.styleable#View_textDirection
*/
+ @ViewDebug.ExportedProperty(category = "text", mapping = {
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"),
+ @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE")
+ })
public int getTextDirection() {
return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED_MASK) >> PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
}
@@ -16722,16 +17070,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return false;
}
- View parent = ((View) mParent);
// Parent has not yet resolved, so we still return the default
- if (!parent.isTextDirectionResolved()) {
+ if (!mParent.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();
+ final int parentResolvedDirection = mParent.getTextDirection();
switch (parentResolvedDirection) {
case TEXT_DIRECTION_FIRST_STRONG:
case TEXT_DIRECTION_ANY_RTL:
@@ -16772,12 +17119,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Check if text direction resolution can be done.
*
* @return true if text direction resolution can be done otherwise return false.
+ *
+ * @hide
*/
- private boolean canResolveTextDirection() {
+ public boolean canResolveTextDirection() {
switch (getRawTextDirection()) {
case TEXT_DIRECTION_INHERIT:
- return (mParent != null) && (mParent instanceof View) &&
- ((View) mParent).canResolveTextDirection();
+ return (mParent != null) && mParent.canResolveTextDirection();
default:
return true;
}
@@ -16807,8 +17155,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* @return true if text direction is resolved.
+ *
+ * @hide
*/
- private boolean isTextDirectionResolved() {
+ public boolean isTextDirectionResolved() {
return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED) == PFLAG2_TEXT_DIRECTION_RESOLVED;
}
@@ -16931,16 +17281,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// 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()) {
+ if (!mParent.isTextAlignmentResolved()) {
mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
// Resolution will need to happen again later
return false;
}
- final int parentResolvedTextAlignment = parent.getTextAlignment();
+ final int parentResolvedTextAlignment = mParent.getTextAlignment();
switch (parentResolvedTextAlignment) {
case TEXT_ALIGNMENT_GRAVITY:
case TEXT_ALIGNMENT_TEXT_START:
@@ -16985,12 +17334,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Check if text alignment resolution can be done.
*
* @return true if text alignment resolution can be done otherwise return false.
+ *
+ * @hide
*/
- private boolean canResolveTextAlignment() {
+ public boolean canResolveTextAlignment() {
switch (getRawTextAlignment()) {
case TEXT_DIRECTION_INHERIT:
- return (mParent != null) && (mParent instanceof View) &&
- ((View) mParent).canResolveTextAlignment();
+ return (mParent != null) && mParent.canResolveTextAlignment();
default:
return true;
}
@@ -17020,8 +17370,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* @return true if text alignment is resolved.
+ *
+ * @hide
*/
- private boolean isTextAlignmentResolved() {
+ public boolean isTextAlignmentResolved() {
return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED) == PFLAG2_TEXT_ALIGNMENT_RESOLVED;
}
@@ -17266,12 +17618,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
* </ul>
*
+ * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
+ * implementation was such that the order of arguments did not matter
+ * and overflow in either value could impact the resulting MeasureSpec.
+ * {@link android.widget.RelativeLayout} was affected by this bug.
+ * Apps targeting API levels greater than 17 will get the fixed, more strict
+ * behavior.</p>
+ *
* @param size the size of the measure specification
* @param mode the mode of the measure specification
* @return the measure specification based on size and mode
*/
public static int makeMeasureSpec(int size, int mode) {
- return size + mode;
+ if (sUseBrokenMakeMeasureSpec) {
+ return size + mode;
+ } else {
+ return (size & ~MODE_MASK) | (mode & MODE_MASK);
+ }
}
/**
@@ -17296,6 +17659,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return (measureSpec & ~MODE_MASK);
}
+ static int adjust(int measureSpec, int delta) {
+ return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec));
+ }
+
/**
* Returns a String representation of the specified measure
* specification.
@@ -17633,25 +18000,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* POOL_LIMIT objects that get reused. This reduces memory allocations
* whenever possible.
*/
- static class InvalidateInfo implements Poolable<InvalidateInfo> {
+ static class InvalidateInfo {
private static final int POOL_LIMIT = 10;
- private static final Pool<InvalidateInfo> sPool = Pools.synchronizedPool(
- Pools.finitePool(new PoolableManager<InvalidateInfo>() {
- public InvalidateInfo newInstance() {
- return new InvalidateInfo();
- }
-
- public void onAcquired(InvalidateInfo element) {
- }
- public void onReleased(InvalidateInfo element) {
- element.target = null;
- }
- }, POOL_LIMIT)
- );
-
- private InvalidateInfo mNext;
- private boolean mIsPooled;
+ private static final SynchronizedPool<InvalidateInfo> sPool =
+ new SynchronizedPool<InvalidateInfo>(POOL_LIMIT);
View target;
@@ -17660,29 +18013,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int right;
int bottom;
- public void setNextPoolable(InvalidateInfo element) {
- mNext = element;
+ public static InvalidateInfo obtain() {
+ InvalidateInfo instance = sPool.acquire();
+ return (instance != null) ? instance : new InvalidateInfo();
}
- public InvalidateInfo getNextPoolable() {
- return mNext;
- }
-
- static InvalidateInfo acquire() {
- return sPool.acquire();
- }
-
- void release() {
+ public void recycle() {
+ target = null;
sPool.release(this);
}
-
- public boolean isPooled() {
- return mIsPooled;
- }
-
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
}
final IWindowSession mSession;
@@ -17697,6 +18036,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
HardwareCanvas mHardwareCanvas;
+ IWindowId mIWindowId;
+ WindowId mWindowId;
+
/**
* The top view of the hierarchy.
*/
@@ -17743,6 +18085,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* For windows that are full-screen but using insets to layout inside
+ * of the screen areas, these are the current insets to appear inside
+ * the overscan area of the display.
+ */
+ final Rect mOverscanInsets = new Rect();
+
+ /**
+ * For windows that are full-screen but using insets to layout inside
* of the screen decorations, these are the current insets for the
* content of the window.
*/
@@ -17844,6 +18193,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
boolean mHasSystemUiListeners;
/**
+ * Set if the window has requested to extend into the overscan region
+ * via WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN.
+ */
+ boolean mOverscanRequested;
+
+ /**
* Set if the visibility of any views has changed.
*/
boolean mViewVisibilityChanged;
@@ -17926,10 +18281,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int mAccessibilityWindowId = View.NO_ID;
/**
- * Whether to ingore not exposed for accessibility Views when
- * reporting the view tree to accessibility services.
+ * Flags related to accessibility processing.
+ *
+ * @see AccessibilityNodeInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ * @see AccessibilityNodeInfo#FLAG_REPORT_VIEW_IDS
*/
- boolean mIncludeNotImportantViews;
+ int mAccessibilityFetchFlags;
/**
* The drawable for highlighting accessibility focus.
@@ -17947,6 +18304,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final Point mPoint = new Point();
/**
+ * Used to track which View originated a requestLayout() call, used when
+ * requestLayout() is called during layout.
+ */
+ View mViewRequestingLayout;
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
@@ -18338,6 +18701,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public AccessibilityNodeProvider getAccessibilityNodeProvider(View host) {
return null;
}
+
+ /**
+ * Returns an {@link AccessibilityNodeInfo} representing the host view from the
+ * point of view of an {@link android.accessibilityservice.AccessibilityService}.
+ * This method is responsible for obtaining an accessibility node info from a
+ * pool of reusable instances and calling
+ * {@link #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} on the host
+ * view to initialize the former.
+ * <p>
+ * <strong>Note:</strong> The client is responsible for recycling the obtained
+ * instance by calling {@link AccessibilityNodeInfo#recycle()} to minimize object
+ * creation.
+ * </p>
+ * <p>
+ * The default implementation behaves as
+ * {@link View#createAccessibilityNodeInfo() View#createAccessibilityNodeInfo()} for
+ * the case of no accessibility delegate been set.
+ * </p>
+ * @return A populated {@link AccessibilityNodeInfo}.
+ *
+ * @see AccessibilityNodeInfo
+ *
+ * @hide
+ */
+ public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) {
+ return host.createAccessibilityNodeInfoInternal();
+ }
}
private class MatchIdPredicate implements Predicate<View> {
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 023e58f..ed128b0 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -22,6 +22,7 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Debug;
+import android.os.Handler;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -43,8 +44,14 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -406,7 +413,7 @@ public class ViewDebug {
view = view.getRootView();
if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
- dump(view, clientStream);
+ dump(view, false, true, clientStream);
} else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
captureLayers(view, new DataOutputStream(clientStream));
} else {
@@ -425,7 +432,8 @@ public class ViewDebug {
}
}
- private static View findView(View root, String parameter) {
+ /** @hide */
+ public static View findView(View root, String parameter) {
// Look by type/hashcode
if (parameter.indexOf('@') != -1) {
final String[] ids = parameter.split("@");
@@ -488,7 +496,8 @@ public class ViewDebug {
}
}
- private static void profileViewAndChildren(final View view, BufferedWriter out)
+ /** @hide */
+ public static void profileViewAndChildren(final View view, BufferedWriter out)
throws IOException {
profileViewAndChildren(view, out, true);
}
@@ -623,7 +632,8 @@ public class ViewDebug {
return duration[0];
}
- private static void captureLayers(View root, final DataOutputStream clientStream)
+ /** @hide */
+ public static void captureLayers(View root, final DataOutputStream clientStream)
throws IOException {
try {
@@ -695,10 +705,21 @@ public class ViewDebug {
view.getViewRootImpl().outputDisplayList(view);
}
+ /** @hide */
+ public static void outputDisplayList(View root, View target) {
+ root.getViewRootImpl().outputDisplayList(target);
+ }
+
private static void capture(View root, final OutputStream clientStream, String parameter)
throws IOException {
final View captureView = findView(root, parameter);
+ capture(root, clientStream, captureView);
+ }
+
+ /** @hide */
+ public static void capture(View root, final OutputStream clientStream, View captureView)
+ throws IOException {
Bitmap b = performViewCapture(captureView, false);
if (b == null) {
@@ -752,14 +773,20 @@ public class ViewDebug {
return null;
}
- private static void dump(View root, OutputStream clientStream) throws IOException {
+ /**
+ * Dumps the view hierarchy starting from the given view.
+ * @hide
+ */
+ public static void dump(View root, boolean skipChildren, boolean includeProperties,
+ OutputStream clientStream) throws IOException {
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
View view = root.getRootView();
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
- dumpViewHierarchyWithProperties(group.getContext(), group, out, 0);
+ dumpViewHierarchy(group.getContext(), group, out, 0,
+ skipChildren, includeProperties);
}
out.write("DONE.");
out.newLine();
@@ -804,9 +831,13 @@ public class ViewDebug {
return view.getClass().getName().equals(className) && view.hashCode() == hashCode;
}
- private static void dumpViewHierarchyWithProperties(Context context, ViewGroup group,
- BufferedWriter out, int level) {
- if (!dumpViewWithProperties(context, group, out, level)) {
+ private static void dumpViewHierarchy(Context context, ViewGroup group,
+ BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
+ if (!dumpView(context, group, out, level, includeProperties)) {
+ return;
+ }
+
+ if (skipChildren) {
return;
}
@@ -814,9 +845,10 @@ public class ViewDebug {
for (int i = 0; i < count; i++) {
final View view = group.getChildAt(i);
if (view instanceof ViewGroup) {
- dumpViewHierarchyWithProperties(context, (ViewGroup) view, out, level + 1);
+ dumpViewHierarchy(context, (ViewGroup) view, out, level + 1, skipChildren,
+ includeProperties);
} else {
- dumpViewWithProperties(context, view, out, level + 1);
+ dumpView(context, view, out, level + 1, includeProperties);
}
}
if (group instanceof HierarchyHandler) {
@@ -824,8 +856,8 @@ public class ViewDebug {
}
}
- private static boolean dumpViewWithProperties(Context context, View view,
- BufferedWriter out, int level) {
+ private static boolean dumpView(Context context, View view,
+ BufferedWriter out, int level, boolean includeProperties) {
try {
for (int i = 0; i < level; i++) {
@@ -835,7 +867,9 @@ public class ViewDebug {
out.write('@');
out.write(Integer.toHexString(view.hashCode()));
out.write(' ');
- dumpViewProperties(context, view, out);
+ if (includeProperties) {
+ dumpViewProperties(context, view, out);
+ }
out.newLine();
} catch (IOException e) {
Log.w("View", "Error while dumping hierarchy tree");
@@ -936,6 +970,48 @@ public class ViewDebug {
} while (klass != Object.class);
}
+ private static Object callMethodOnAppropriateTheadBlocking(final Method method,
+ final Object object) throws IllegalAccessException, InvocationTargetException,
+ TimeoutException {
+ if (!(object instanceof View)) {
+ return method.invoke(object, (Object[]) null);
+ }
+
+ final View view = (View) object;
+ Callable<Object> callable = new Callable<Object>() {
+ @Override
+ public Object call() throws IllegalAccessException, InvocationTargetException {
+ return method.invoke(view, (Object[]) null);
+ }
+ };
+ FutureTask<Object> future = new FutureTask<Object>(callable);
+ // Try to use the handler provided by the view
+ Handler handler = view.getHandler();
+ // Fall back on using the main thread
+ if (handler == null) {
+ handler = new Handler(android.os.Looper.getMainLooper());
+ }
+ handler.post(future);
+ while (true) {
+ try {
+ return future.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
+ } catch (ExecutionException e) {
+ Throwable t = e.getCause();
+ if (t instanceof IllegalAccessException) {
+ throw (IllegalAccessException)t;
+ }
+ if (t instanceof InvocationTargetException) {
+ throw (InvocationTargetException)t;
+ }
+ throw new RuntimeException("Unexpected exception", t);
+ } catch (InterruptedException e) {
+ // Call get again
+ } catch (CancellationException e) {
+ throw new RuntimeException("Unexpected cancellation exception", e);
+ }
+ }
+ }
+
private static void exportMethods(Context context, Object view, BufferedWriter out,
Class<?> klass, String prefix) throws IOException {
@@ -946,8 +1022,7 @@ public class ViewDebug {
final Method method = methods[i];
//noinspection EmptyCatchBlock
try {
- // TODO: This should happen on the UI thread
- Object methodValue = method.invoke(view, (Object[]) null);
+ Object methodValue = callMethodOnAppropriateTheadBlocking(method, view);
final Class<?> returnType = method.getReturnType();
final ExportedProperty property = sAnnotations.get(method);
String categoryPrefix =
@@ -1005,6 +1080,7 @@ public class ViewDebug {
writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
+ } catch (TimeoutException e) {
}
}
}
@@ -1026,8 +1102,7 @@ public class ViewDebug {
String categoryPrefix =
property.category().length() != 0 ? property.category() + ":" : "";
- if (type == int.class) {
-
+ if (type == int.class || type == byte.class) {
if (property.resolveId() && context != null) {
final int id = field.getInt(view);
fieldValue = resolveId(context, id);
@@ -1347,4 +1422,68 @@ public class ViewDebug {
sb.append(capturedViewExportMethods(view, klass, ""));
Log.d(tag, sb.toString());
}
+
+ /**
+ * Invoke a particular method on given view.
+ * The given method is always invoked on the UI thread. The caller thread will stall until the
+ * method invocation is complete. Returns an object equal to the result of the method
+ * invocation, null if the method is declared to return void
+ * @throws Exception if the method invocation caused any exception
+ * @hide
+ */
+ public static Object invokeViewMethod(final View view, final Method method,
+ final Object[] args) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<Object> result = new AtomicReference<Object>();
+ final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
+
+ view.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ result.set(method.invoke(view, args));
+ } catch (InvocationTargetException e) {
+ exception.set(e.getCause());
+ } catch (Exception e) {
+ exception.set(e);
+ }
+
+ latch.countDown();
+ }
+ });
+
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ if (exception.get() != null) {
+ throw new RuntimeException(exception.get());
+ }
+
+ return result.get();
+ }
+
+ /**
+ * @hide
+ */
+ public static void setLayoutParameter(final View view, final String param, final int value)
+ throws NoSuchFieldException, IllegalAccessException {
+ final ViewGroup.LayoutParams p = view.getLayoutParams();
+ final Field f = p.getClass().getField(param);
+ if (f.getType() != int.class) {
+ throw new RuntimeException("Only integer layout parameters can be set. Field "
+ + param + " is of type " + f.getType().getSimpleName());
+ }
+
+ f.set(p, Integer.valueOf(value));
+
+ view.post(new Runnable() {
+ @Override
+ public void run() {
+ view.setLayoutParams(p);
+ }
+ });
+ }
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index dbbcde6..95d65eb 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -35,6 +35,7 @@ import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.Pools.SynchronizedPool;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -50,6 +51,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
/**
* <p>
* A <code>ViewGroup</code> is a special view that can contain other views
@@ -69,6 +72,22 @@ import java.util.HashSet;
* <a href="{@docRoot}guide/topics/ui/declaring-layout.html">XML Layouts</a> developer
* guide.</p></div>
*
+ * <p>Here is a complete implementation of a custom ViewGroup that implements
+ * a simple {@link android.widget.FrameLayout} along with the ability to stack
+ * children in left and right gutters.</p>
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/view/CustomLayout.java
+ * Complete}
+ *
+ * <p>If you are implementing XML layout attributes as shown in the example, this is the
+ * corresponding definition for them that would go in <code>res/values/attrs.xml</code>:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/values/attrs.xml CustomLayout}
+ *
+ * <p>Finally the layout manager can be used in an XML layout like so:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/layout/custom_layout.xml Complete}
+ *
* @attr ref android.R.styleable#ViewGroup_clipChildren
* @attr ref android.R.styleable#ViewGroup_clipToPadding
* @attr ref android.R.styleable#ViewGroup_layoutAnimation
@@ -78,11 +97,15 @@ import java.util.HashSet;
* @attr ref android.R.styleable#ViewGroup_addStatesFromChildren
* @attr ref android.R.styleable#ViewGroup_descendantFocusability
* @attr ref android.R.styleable#ViewGroup_animateLayoutChanges
+ * @attr ref android.R.styleable#ViewGroup_splitMotionEvents
+ * @attr ref android.R.styleable#ViewGroup_layoutMode
*/
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
private static final String TAG = "ViewGroup";
private static final boolean DBG = false;
+ /** @hide */
+ public static boolean DEBUG_DRAW = false;
/**
* Views which have been hidden or removed which need to be animated on
@@ -180,10 +203,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
})
protected int mGroupFlags;
- /*
- * The layout mode: either {@link #CLIP_BOUNDS} or {@link #OPTICAL_BOUNDS}
+ /**
+ * Either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
*/
- private int mLayoutMode = CLIP_BOUNDS;
+ private int mLayoutMode = DEFAULT_LAYOUT_MODE;
/**
* NOTE: If you change the flags below make sure to reflect the changes
@@ -356,20 +379,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* This constant is a {@link #setLayoutMode(int) layoutMode}.
* Clip bounds are the raw values of {@link #getLeft() left}, {@link #getTop() top},
* {@link #getRight() right} and {@link #getBottom() bottom}.
- *
- * @hide
*/
- public static final int CLIP_BOUNDS = 0;
+ public static final int LAYOUT_MODE_CLIP_BOUNDS = 0;
/**
* This constant is a {@link #setLayoutMode(int) layoutMode}.
* Optical bounds describe where a widget appears to be. They sit inside the clip
* bounds which need to cover a larger area to allow other effects,
* such as shadows and glows, to be drawn.
- *
- * @hide
*/
- public static final int OPTICAL_BOUNDS = 1;
+ public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1;
+
+ /** @hide */
+ public static int DEFAULT_LAYOUT_MODE = LAYOUT_MODE_CLIP_BOUNDS;
/**
* We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL
@@ -386,10 +408,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private View[] mChildren;
// Number of valid children in the mChildren array, the rest should be null or not
// considered as children
+ private int mChildrenCount;
- private boolean mLayoutSuppressed = false;
+ // Whether layout calls are currently being suppressed, controlled by calls to
+ // suppressLayout()
+ boolean mSuppressLayout = false;
- private int mChildrenCount;
+ // Whether any layout calls have actually been suppressed while mSuppressLayout
+ // has been true. This tracks whether we need to issue a requestLayout() when
+ // layout is later re-enabled.
+ private boolean mLayoutCalledWhileSuppressed = false;
private static final int ARRAY_INITIAL_CAPACITY = 12;
private static final int ARRAY_CAPACITY_INCREMENT = 12;
@@ -434,7 +462,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
private boolean debugDraw() {
- return mAttachInfo != null && mAttachInfo.mDebugLayout;
+ return DEBUG_DRAW || mAttachInfo != null && mAttachInfo.mDebugLayout;
}
private void initViewGroup() {
@@ -504,6 +532,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
setLayoutTransition(new LayoutTransition());
}
break;
+ case R.styleable.ViewGroup_layoutMode:
+ setLayoutMode(a.getInt(attr, DEFAULT_LAYOUT_MODE));
+ break;
}
}
@@ -904,8 +935,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ /** @hide */
@Override
- View findViewByAccessibilityIdTraversal(int accessibilityId) {
+ public View findViewByAccessibilityIdTraversal(int accessibilityId) {
View foundView = super.findViewByAccessibilityIdTraversal(accessibilityId);
if (foundView != null) {
return foundView;
@@ -1147,8 +1179,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
mDragNotifiedChildren.clear();
- mCurrentDrag.recycle();
- mCurrentDrag = null;
+ if (mCurrentDrag != null) {
+ mCurrentDrag.recycle();
+ mCurrentDrag = null;
+ }
}
// We consider drag-ended to have been handled if one of our children
@@ -1433,10 +1467,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float y = event.getY();
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
+ final boolean customChildOrder = isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
HoverTarget lastHoverTarget = null;
for (int i = childrenCount - 1; i >= 0; i--) {
- final View child = children[i];
+ final int childIndex = customChildOrder
+ ? getChildDrawingOrder(childrenCount, i) : i;
+ final View child = children[childIndex];
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
@@ -1846,12 +1883,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
- if (childrenCount != 0) {
+ if (newTouchTarget == null && childrenCount != 0) {
+ final float x = ev.getX(actionIndex);
+ final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
- final float x = ev.getX(actionIndex);
- final float y = ev.getY(actionIndex);
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
@@ -1913,7 +1950,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
- || intercepted;
+ || intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
@@ -2214,6 +2251,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @param split <code>true</code> to allow MotionEvents to be split and dispatched to multiple
* child views. <code>false</code> to only allow one child view to be the target of
* any MotionEvent received by this ViewGroup.
+ * @attr ref android.R.styleable#ViewGroup_splitMotionEvents
*/
public void setMotionEventSplittingEnabled(boolean split) {
// TODO Applications really shouldn't change this setting mid-touch event,
@@ -2420,7 +2458,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info,
- visibility | (child.mViewFlags&VISIBILITY_MASK));
+ visibility | (child.mViewFlags & VISIBILITY_MASK));
}
}
@@ -2515,7 +2553,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
exitHoverTargets();
// In case view is detached while transition is running
- mLayoutSuppressed = false;
+ mLayoutCalledWhileSuppressed = false;
// Tear down our drag tracking
mDragNotifiedChildren = null;
@@ -2682,20 +2720,89 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return b;
}
- private static void drawRect(Canvas canvas, int x1, int y1, int x2, int y2, int color) {
- Paint paint = getDebugPaint();
- paint.setColor(color);
+ /** Return true if this ViewGroup is laying out using optical bounds. */
+ boolean isLayoutModeOptical() {
+ return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;
+ }
- canvas.drawLines(getDebugLines(x1, y1, x2, y2), paint);
+ Insets computeOpticalInsets() {
+ if (isLayoutModeOptical()) {
+ int left = 0;
+ int top = 0;
+ int right = 0;
+ int bottom = 0;
+ for (int i = 0; i < mChildrenCount; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() == VISIBLE) {
+ Insets insets = child.getOpticalInsets();
+ left = Math.max(left, insets.left);
+ top = Math.max(top, insets.top);
+ right = Math.max(right, insets.right);
+ bottom = Math.max(bottom, insets.bottom);
+ }
+ }
+ return Insets.of(left, top, right, bottom);
+ } else {
+ return Insets.NONE;
+ }
+ }
+
+ private static void fillRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
+ if (x1 != x2 && y1 != y2) {
+ if (x1 > x2) {
+ int tmp = x1; x1 = x2; x2 = tmp;
+ }
+ if (y1 > y2) {
+ int tmp = y1; y1 = y2; y2 = tmp;
+ }
+ canvas.drawRect(x1, y1, x2, y2, paint);
+ }
+ }
+
+ private static int sign(int x) {
+ return (x >= 0) ? 1 : -1;
+ }
+
+ private static void drawCorner(Canvas c, Paint paint, int x1, int y1, int dx, int dy, int lw) {
+ fillRect(c, paint, x1, y1, x1 + dx, y1 + lw * sign(dy));
+ fillRect(c, paint, x1, y1, x1 + lw * sign(dx), y1 + dy);
+ }
+
+ private int dipsToPixels(int dips) {
+ float scale = getContext().getResources().getDisplayMetrics().density;
+ return (int) (dips * scale + 0.5f);
+ }
+
+ private void drawRectCorners(Canvas canvas, int x1, int y1, int x2, int y2, Paint paint,
+ int lineLength, int lineWidth) {
+ drawCorner(canvas, paint, x1, y1, lineLength, lineLength, lineWidth);
+ drawCorner(canvas, paint, x1, y2, lineLength, -lineLength, lineWidth);
+ drawCorner(canvas, paint, x2, y1, -lineLength, lineLength, lineWidth);
+ drawCorner(canvas, paint, x2, y2, -lineLength, -lineLength, lineWidth);
+ }
+
+ private static void fillDifference(Canvas canvas,
+ int x2, int y2, int x3, int y3,
+ int dx1, int dy1, int dx2, int dy2, Paint paint) {
+ int x1 = x2 - dx1;
+ int y1 = y2 - dy1;
+
+ int x4 = x3 + dx2;
+ int y4 = y3 + dy2;
+
+ fillRect(canvas, paint, x1, y1, x4, y2);
+ fillRect(canvas, paint, x1, y2, x2, y3);
+ fillRect(canvas, paint, x3, y2, x4, y3);
+ fillRect(canvas, paint, x1, y3, x4, y4);
}
/**
* @hide
*/
- protected void onDebugDrawMargins(Canvas canvas) {
+ protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
for (int i = 0; i < getChildCount(); i++) {
View c = getChildAt(i);
- c.getLayoutParams().onDebugDraw(c, canvas);
+ c.getLayoutParams().onDebugDraw(c, canvas, paint);
}
}
@@ -2703,26 +2810,45 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @hide
*/
protected void onDebugDraw(Canvas canvas) {
+ Paint paint = getDebugPaint();
+
// Draw optical bounds
- if (getLayoutMode() == OPTICAL_BOUNDS) {
+ {
+ paint.setColor(Color.RED);
+ paint.setStyle(Paint.Style.STROKE);
+
for (int i = 0; i < getChildCount(); i++) {
View c = getChildAt(i);
Insets insets = c.getOpticalInsets();
- drawRect(canvas,
- c.getLeft() + insets.left,
- c.getTop() + insets.top,
- c.getRight() - insets.right,
- c.getBottom() - insets.bottom, Color.RED);
+
+ drawRect(canvas, paint,
+ c.getLeft() + insets.left,
+ c.getTop() + insets.top,
+ c.getRight() - insets.right - 1,
+ c.getBottom() - insets.bottom - 1);
}
}
// Draw margins
- onDebugDrawMargins(canvas);
+ {
+ paint.setColor(Color.argb(63, 255, 0, 255));
+ paint.setStyle(Paint.Style.FILL);
- // Draw bounds
- for (int i = 0; i < getChildCount(); i++) {
- View c = getChildAt(i);
- drawRect(canvas, c.getLeft(), c.getTop(), c.getRight(), c.getBottom(), Color.BLUE);
+ onDebugDrawMargins(canvas, paint);
+ }
+
+ // Draw clip bounds
+ {
+ paint.setColor(Color.rgb(63, 127, 255));
+ paint.setStyle(Paint.Style.FILL);
+
+ int lineLength = dipsToPixels(8);
+ int lineWidth = dipsToPixels(1);
+ for (int i = 0; i < getChildCount(); i++) {
+ View c = getChildAt(i);
+ drawRectCorners(canvas, c.getLeft(), c.getTop(), c.getRight(), c.getBottom(),
+ paint, lineLength, lineWidth);
+ }
}
}
@@ -2848,6 +2974,30 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * Returns the ViewGroupOverlay for this view group, creating it if it does
+ * not yet exist. In addition to {@link ViewOverlay}'s support for drawables,
+ * {@link ViewGroupOverlay} allows views to be added to the overlay. These
+ * views, like overlay drawables, are visual-only; they do not receive input
+ * events and should not be used as anything other than a temporary
+ * representation of a view in a parent container, such as might be used
+ * by an animation effect.
+ *
+ * <p>Note: Overlays do not currently work correctly with {@link
+ * SurfaceView} or {@link TextureView}; contents in overlays for these
+ * types of views may not display correctly.</p>
+ *
+ * @return The ViewGroupOverlay object for this view.
+ * @see ViewGroupOverlay
+ */
+ @Override
+ public ViewGroupOverlay getOverlay() {
+ if (mOverlay == null) {
+ mOverlay = new ViewGroupOverlay(mContext, this);
+ }
+ return (ViewGroupOverlay) mOverlay;
+ }
+
+ /**
* Returns the index of the child to draw for this iteration. Override this
* if you want to change the drawing order of children. By default, it
* returns i.
@@ -2911,6 +3061,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
child.mRecreateDisplayList = false;
}
}
+ if (mOverlay != null) {
+ View overlayView = mOverlay.getOverlayView();
+ overlayView.mRecreateDisplayList = (overlayView.mPrivateFlags & PFLAG_INVALIDATED)
+ == PFLAG_INVALIDATED;
+ overlayView.mPrivateFlags &= ~PFLAG_INVALIDATED;
+ overlayView.getDisplayList();
+ overlayView.mRecreateDisplayList = false;
+ }
}
/**
@@ -2929,6 +3087,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * Returns whether ths group's children are clipped to their bounds before drawing.
+ * The default value is true.
+ * @see #setClipChildren(boolean)
+ *
+ * @return True if the group's children will be clipped to their bounds,
+ * false otherwise.
+ */
+ public boolean getClipChildren() {
+ return ((mGroupFlags & FLAG_CLIP_CHILDREN) != 0);
+ }
+
+ /**
* By default, children are clipped to their bounds before drawing. This
* allows view groups to override this behavior for animations, etc.
*
@@ -2943,7 +3113,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = 0; i < mChildrenCount; ++i) {
View child = getChildAt(i);
if (child.mDisplayList != null) {
- child.mDisplayList.setClipChildren(clipChildren);
+ child.mDisplayList.setClipToBounds(clipChildren);
}
}
}
@@ -3604,7 +3774,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
clearChildFocus = true;
}
- view.clearAccessibilityFocus();
+ if (view.isAccessibilityFocused()) {
+ view.clearAccessibilityFocus();
+ }
cancelTouchTarget(view);
cancelHoverTarget(view);
@@ -3620,20 +3792,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
childHasTransientStateChanged(view, false);
}
- onViewRemoved(view);
-
needGlobalAttributesUpdate(false);
removeFromArray(index);
if (clearChildFocus) {
clearChildFocus(view);
- ensureInputFocusOnFirstFocusable();
+ if (!rootViewRequestFocus()) {
+ notifyGlobalFocusCleared(this);
+ }
}
- if (view.isAccessibilityFocused()) {
- view.clearAccessibilityFocus();
- }
+ onViewRemoved(view);
}
/**
@@ -3672,7 +3842,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private void removeViewsInternal(int start, int count) {
final View focused = mFocused;
final boolean detach = mAttachInfo != null;
- View clearChildFocus = null;
+ boolean clearChildFocus = false;
final View[] children = mChildren;
final int end = start + count;
@@ -3686,10 +3856,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (view == focused) {
view.unFocus();
- clearChildFocus = view;
+ clearChildFocus = true;
}
- view.clearAccessibilityFocus();
+ if (view.isAccessibilityFocused()) {
+ view.clearAccessibilityFocus();
+ }
cancelTouchTarget(view);
cancelHoverTarget(view);
@@ -3712,9 +3884,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
removeFromArray(start, count);
- if (clearChildFocus != null) {
- clearChildFocus(clearChildFocus);
- ensureInputFocusOnFirstFocusable();
+ if (clearChildFocus) {
+ clearChildFocus(focused);
+ if (!rootViewRequestFocus()) {
+ notifyGlobalFocusCleared(focused);
+ }
}
}
@@ -3756,7 +3930,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View focused = mFocused;
final boolean detach = mAttachInfo != null;
- View clearChildFocus = null;
+ boolean clearChildFocus = false;
needGlobalAttributesUpdate(false);
@@ -3769,10 +3943,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (view == focused) {
view.unFocus();
- clearChildFocus = view;
+ clearChildFocus = true;
}
- view.clearAccessibilityFocus();
+ if (view.isAccessibilityFocused()) {
+ view.clearAccessibilityFocus();
+ }
cancelTouchTarget(view);
cancelHoverTarget(view);
@@ -3794,9 +3970,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
children[i] = null;
}
- if (clearChildFocus != null) {
- clearChildFocus(clearChildFocus);
- ensureInputFocusOnFirstFocusable();
+ if (clearChildFocus) {
+ clearChildFocus(focused);
+ if (!rootViewRequestFocus()) {
+ notifyGlobalFocusCleared(focused);
+ }
}
}
@@ -4105,6 +4283,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
FLAG_OPTIMIZE_INVALIDATE) {
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
+ if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
+ dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
+ }
final int left = mLeft;
final int top = mTop;
@@ -4204,6 +4385,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
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) {
+ dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
+ }
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0 ||
dirty.intersect(0, 0, mRight - mLeft, mBottom - mTop)) {
@@ -4306,16 +4490,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public void offsetChildrenTopAndBottom(int offset) {
final int count = mChildrenCount;
final View[] children = mChildren;
+ boolean invalidate = false;
for (int i = 0; i < count; i++) {
final View v = children[i];
v.mTop += offset;
v.mBottom += offset;
if (v.mDisplayList != null) {
- v.mDisplayList.offsetTopBottom(offset);
- invalidateViewProperty(false, false);
+ invalidate = true;
+ v.mDisplayList.offsetTopAndBottom(offset);
}
}
+
+ if (invalidate) {
+ invalidateViewProperty(false, false);
+ }
}
/**
@@ -4366,14 +4555,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
public final void layout(int l, int t, int r, int b) {
- if (mTransition == null || !mTransition.isChangingLayout()) {
+ if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
- mLayoutSuppressed = true;
+ mLayoutCalledWhileSuppressed = true;
}
}
@@ -4610,13 +4799,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Returns the basis of alignment during layout operations on this view group:
- * either {@link #CLIP_BOUNDS} or {@link #OPTICAL_BOUNDS}.
+ * either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
*
* @return the layout mode to use during layout operations
*
* @see #setLayoutMode(int)
- *
- * @hide
*/
public int getLayoutMode() {
return mLayoutMode;
@@ -4624,15 +4811,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Sets the basis of alignment during the layout of this view group.
- * Valid values are either {@link #CLIP_BOUNDS} or {@link #OPTICAL_BOUNDS}.
+ * Valid values are either {@link #LAYOUT_MODE_CLIP_BOUNDS} or
+ * {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
* <p>
- * The default is {@link #CLIP_BOUNDS}.
+ * The default is {@link #LAYOUT_MODE_CLIP_BOUNDS}.
*
* @param layoutMode the layout mode to use during layout operations
*
* @see #getLayoutMode()
- *
- * @hide
+ * @attr ref android.R.styleable#ViewGroup_layoutMode
*/
public void setLayoutMode(int layoutMode) {
if (mLayoutMode != layoutMode) {
@@ -5052,9 +5239,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@Override
public void endTransition(LayoutTransition transition, ViewGroup container,
View view, int transitionType) {
- if (mLayoutSuppressed && !transition.isChangingLayout()) {
+ if (mLayoutCalledWhileSuppressed && !transition.isChangingLayout()) {
requestLayout();
- mLayoutSuppressed = false;
+ mLayoutCalledWhileSuppressed = false;
}
if (transitionType == LayoutTransition.DISAPPEARING && mTransitioningViews != null) {
endViewTransition(view);
@@ -5063,6 +5250,24 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
};
/**
+ * Tells this ViewGroup to suppress all layout() calls until layout
+ * suppression is disabled with a later call to suppressLayout(false).
+ * When layout suppression is disabled, a requestLayout() call is sent
+ * if layout() was attempted while layout was being suppressed.
+ *
+ * @hide
+ */
+ public void suppressLayout(boolean suppress) {
+ mSuppressLayout = suppress;
+ if (!suppress) {
+ if (mLayoutCalledWhileSuppressed) {
+ requestLayout();
+ mLayoutCalledWhileSuppressed = false;
+ }
+ }
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -5258,15 +5463,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @hide
*/
@Override
- public void resolveRtlPropertiesIfNeeded() {
- super.resolveRtlPropertiesIfNeeded();
- int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (child.isLayoutDirectionInherited()) {
- child.resolveRtlPropertiesIfNeeded();
+ public boolean resolveRtlPropertiesIfNeeded() {
+ final boolean result = super.resolveRtlPropertiesIfNeeded();
+ // We dont need to resolve the children RTL properties if nothing has changed for the parent
+ if (result) {
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.isLayoutDirectionInherited()) {
+ child.resolveRtlPropertiesIfNeeded();
+ }
}
}
+ return result;
}
/**
@@ -5650,7 +5859,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*
* @hide
*/
- public void onDebugDraw(View view, Canvas canvas) {
+ public void onDebugDraw(View view, Canvas canvas, Paint paint) {
}
/**
@@ -5717,7 +5926,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* to this field.
*/
@ViewDebug.ExportedProperty(category = "layout")
- private int startMargin = DEFAULT_RELATIVE;
+ private int startMargin = DEFAULT_MARGIN_RELATIVE;
/**
* The end margin in pixels of the child.
@@ -5725,21 +5934,48 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* to this field.
*/
@ViewDebug.ExportedProperty(category = "layout")
- private int endMargin = DEFAULT_RELATIVE;
+ private int endMargin = DEFAULT_MARGIN_RELATIVE;
/**
* The default start and end margin.
* @hide
*/
- public static final int DEFAULT_RELATIVE = Integer.MIN_VALUE;
+ public static final int DEFAULT_MARGIN_RELATIVE = Integer.MIN_VALUE;
- private int initialLeftMargin;
- private int initialRightMargin;
+ /**
+ * Bit 0: layout direction
+ * Bit 1: layout direction
+ * Bit 2: left margin undefined
+ * Bit 3: right margin undefined
+ * Bit 4: is RTL compatibility mode
+ * Bit 5: need resolution
+ *
+ * Bit 6 to 7 not used
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "layout", flagMapping = {
+ @ViewDebug.FlagToString(mask = LAYOUT_DIRECTION_MASK,
+ equals = LAYOUT_DIRECTION_MASK, name = "LAYOUT_DIRECTION"),
+ @ViewDebug.FlagToString(mask = LEFT_MARGIN_UNDEFINED_MASK,
+ equals = LEFT_MARGIN_UNDEFINED_MASK, name = "LEFT_MARGIN_UNDEFINED_MASK"),
+ @ViewDebug.FlagToString(mask = RIGHT_MARGIN_UNDEFINED_MASK,
+ equals = RIGHT_MARGIN_UNDEFINED_MASK, name = "RIGHT_MARGIN_UNDEFINED_MASK"),
+ @ViewDebug.FlagToString(mask = RTL_COMPATIBILITY_MODE_MASK,
+ equals = RTL_COMPATIBILITY_MODE_MASK, name = "RTL_COMPATIBILITY_MODE_MASK"),
+ @ViewDebug.FlagToString(mask = NEED_RESOLUTION_MASK,
+ equals = NEED_RESOLUTION_MASK, name = "NEED_RESOLUTION_MASK")
+ })
+ byte mMarginFlags;
- private static int LAYOUT_DIRECTION_UNDEFINED = -1;
+ private static final int LAYOUT_DIRECTION_MASK = 0x00000003;
+ private static final int LEFT_MARGIN_UNDEFINED_MASK = 0x00000004;
+ private static final int RIGHT_MARGIN_UNDEFINED_MASK = 0x00000008;
+ private static final int RTL_COMPATIBILITY_MODE_MASK = 0x00000010;
+ private static final int NEED_RESOLUTION_MASK = 0x00000020;
- // Layout direction undefined by default
- private int layoutDirection = LAYOUT_DIRECTION_UNDEFINED;
+ private static final int DEFAULT_MARGIN_RESOLVED = 0;
+ private static final int UNDEFINED_MARGIN = DEFAULT_MARGIN_RELATIVE;
/**
* Creates a new set of layout parameters. The values are extracted from
@@ -5766,21 +6002,47 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
bottomMargin = margin;
} else {
leftMargin = a.getDimensionPixelSize(
- R.styleable.ViewGroup_MarginLayout_layout_marginLeft, 0);
- topMargin = a.getDimensionPixelSize(
- R.styleable.ViewGroup_MarginLayout_layout_marginTop, 0);
+ R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
+ UNDEFINED_MARGIN);
+ if (leftMargin == UNDEFINED_MARGIN) {
+ mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
+ leftMargin = DEFAULT_MARGIN_RESOLVED;
+ }
rightMargin = a.getDimensionPixelSize(
- R.styleable.ViewGroup_MarginLayout_layout_marginRight, 0);
+ R.styleable.ViewGroup_MarginLayout_layout_marginRight,
+ UNDEFINED_MARGIN);
+ if (rightMargin == UNDEFINED_MARGIN) {
+ mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
+ rightMargin = DEFAULT_MARGIN_RESOLVED;
+ }
+
+ topMargin = a.getDimensionPixelSize(
+ R.styleable.ViewGroup_MarginLayout_layout_marginTop,
+ DEFAULT_MARGIN_RESOLVED);
bottomMargin = a.getDimensionPixelSize(
- R.styleable.ViewGroup_MarginLayout_layout_marginBottom, 0);
+ R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
+ DEFAULT_MARGIN_RESOLVED);
+
startMargin = a.getDimensionPixelSize(
- R.styleable.ViewGroup_MarginLayout_layout_marginStart, DEFAULT_RELATIVE);
+ R.styleable.ViewGroup_MarginLayout_layout_marginStart,
+ DEFAULT_MARGIN_RELATIVE);
endMargin = a.getDimensionPixelSize(
- R.styleable.ViewGroup_MarginLayout_layout_marginEnd, DEFAULT_RELATIVE);
+ R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
+ DEFAULT_MARGIN_RELATIVE);
+
+ if (isMarginRelative()) {
+ mMarginFlags |= NEED_RESOLUTION_MASK;
+ }
}
- initialLeftMargin = leftMargin;
- initialRightMargin = rightMargin;
+ final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
+ final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
+ if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
+ mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
+ }
+
+ // Layout direction is LTR by default
+ mMarginFlags |= LAYOUT_DIRECTION_LTR;
a.recycle();
}
@@ -5790,6 +6052,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
public MarginLayoutParams(int width, int height) {
super(width, height);
+
+ mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
+ mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
+
+ mMarginFlags &= ~NEED_RESOLUTION_MASK;
+ mMarginFlags &= ~RTL_COMPATIBILITY_MODE_MASK;
}
/**
@@ -5808,10 +6076,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
this.startMargin = source.startMargin;
this.endMargin = source.endMargin;
- this.initialLeftMargin = source.leftMargin;
- this.initialRightMargin = source.rightMargin;
-
- setLayoutDirection(source.layoutDirection);
+ this.mMarginFlags = source.mMarginFlags;
}
/**
@@ -5819,6 +6084,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
public MarginLayoutParams(LayoutParams source) {
super(source);
+
+ mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
+ mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
+
+ mMarginFlags &= ~NEED_RESOLUTION_MASK;
+ mMarginFlags &= ~RTL_COMPATIBILITY_MODE_MASK;
}
/**
@@ -5841,8 +6112,13 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
topMargin = top;
rightMargin = right;
bottomMargin = bottom;
- initialLeftMargin = left;
- initialRightMargin = right;
+ mMarginFlags &= ~LEFT_MARGIN_UNDEFINED_MASK;
+ mMarginFlags &= ~RIGHT_MARGIN_UNDEFINED_MASK;
+ if (isMarginRelative()) {
+ mMarginFlags |= NEED_RESOLUTION_MASK;
+ } else {
+ mMarginFlags &= ~NEED_RESOLUTION_MASK;
+ }
}
/**
@@ -5868,8 +6144,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
topMargin = top;
endMargin = end;
bottomMargin = bottom;
- initialLeftMargin = 0;
- initialRightMargin = 0;
+ mMarginFlags |= NEED_RESOLUTION_MASK;
}
/**
@@ -5881,6 +6156,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
public void setMarginStart(int start) {
startMargin = start;
+ mMarginFlags |= NEED_RESOLUTION_MASK;
}
/**
@@ -5891,8 +6167,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @return the start margin in pixels.
*/
public int getMarginStart() {
- if (startMargin != DEFAULT_RELATIVE) return startMargin;
- switch(layoutDirection) {
+ if (startMargin != DEFAULT_MARGIN_RELATIVE) return startMargin;
+ if ((mMarginFlags & NEED_RESOLUTION_MASK) == NEED_RESOLUTION_MASK) {
+ doResolveMargins();
+ }
+ switch(mMarginFlags & LAYOUT_DIRECTION_MASK) {
case View.LAYOUT_DIRECTION_RTL:
return rightMargin;
case View.LAYOUT_DIRECTION_LTR:
@@ -5910,6 +6189,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
public void setMarginEnd(int end) {
endMargin = end;
+ mMarginFlags |= NEED_RESOLUTION_MASK;
}
/**
@@ -5920,8 +6200,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @return the end margin in pixels.
*/
public int getMarginEnd() {
- if (endMargin != DEFAULT_RELATIVE) return endMargin;
- switch(layoutDirection) {
+ if (endMargin != DEFAULT_MARGIN_RELATIVE) return endMargin;
+ if ((mMarginFlags & NEED_RESOLUTION_MASK) == NEED_RESOLUTION_MASK) {
+ doResolveMargins();
+ }
+ switch(mMarginFlags & LAYOUT_DIRECTION_MASK) {
case View.LAYOUT_DIRECTION_RTL:
return leftMargin;
case View.LAYOUT_DIRECTION_LTR:
@@ -5939,7 +6222,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @return true if either marginStart or marginEnd has been set.
*/
public boolean isMarginRelative() {
- return (startMargin != DEFAULT_RELATIVE) || (endMargin != DEFAULT_RELATIVE);
+ return (startMargin != DEFAULT_MARGIN_RELATIVE || endMargin != DEFAULT_MARGIN_RELATIVE);
}
/**
@@ -5951,7 +6234,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public void setLayoutDirection(int layoutDirection) {
if (layoutDirection != View.LAYOUT_DIRECTION_LTR &&
layoutDirection != View.LAYOUT_DIRECTION_RTL) return;
- this.layoutDirection = layoutDirection;
+ if (layoutDirection != (mMarginFlags & LAYOUT_DIRECTION_MASK)) {
+ mMarginFlags &= ~LAYOUT_DIRECTION_MASK;
+ mMarginFlags |= (layoutDirection & LAYOUT_DIRECTION_MASK);
+ if (isMarginRelative()) {
+ mMarginFlags |= NEED_RESOLUTION_MASK;
+ } else {
+ mMarginFlags &= ~NEED_RESOLUTION_MASK;
+ }
+ }
}
/**
@@ -5961,7 +6252,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @return the layout direction.
*/
public int getLayoutDirection() {
- return layoutDirection;
+ return (mMarginFlags & LAYOUT_DIRECTION_MASK);
}
/**
@@ -5972,38 +6263,74 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
public void resolveLayoutDirection(int layoutDirection) {
setLayoutDirection(layoutDirection);
- if (!isMarginRelative()) return;
+ // No relative margin or pre JB-MR1 case or no need to resolve, just dont do anything
+ // Will use the left and right margins if no relative margin is defined.
+ if (!isMarginRelative() ||
+ (mMarginFlags & NEED_RESOLUTION_MASK) != NEED_RESOLUTION_MASK) return;
- switch(layoutDirection) {
- case View.LAYOUT_DIRECTION_RTL:
- leftMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : initialLeftMargin;
- rightMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : initialRightMargin;
- break;
- case View.LAYOUT_DIRECTION_LTR:
- default:
- leftMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : initialLeftMargin;
- rightMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : initialRightMargin;
- break;
+ // Proceed with resolution
+ doResolveMargins();
+ }
+
+ private void doResolveMargins() {
+ if ((mMarginFlags & RTL_COMPATIBILITY_MODE_MASK) == RTL_COMPATIBILITY_MODE_MASK) {
+ // if left or right margins are not defined and if we have some start or end margin
+ // defined then use those start and end margins.
+ if ((mMarginFlags & LEFT_MARGIN_UNDEFINED_MASK) == LEFT_MARGIN_UNDEFINED_MASK
+ && startMargin > DEFAULT_MARGIN_RELATIVE) {
+ leftMargin = startMargin;
+ }
+ if ((mMarginFlags & RIGHT_MARGIN_UNDEFINED_MASK) == RIGHT_MARGIN_UNDEFINED_MASK
+ && endMargin > DEFAULT_MARGIN_RELATIVE) {
+ rightMargin = endMargin;
+ }
+ } else {
+ // We have some relative margins (either the start one or the end one or both). So use
+ // them and override what has been defined for left and right margins. If either start
+ // or end margin is not defined, just set it to default "0".
+ switch(mMarginFlags & LAYOUT_DIRECTION_MASK) {
+ case View.LAYOUT_DIRECTION_RTL:
+ leftMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ?
+ endMargin : DEFAULT_MARGIN_RESOLVED;
+ rightMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ?
+ startMargin : DEFAULT_MARGIN_RESOLVED;
+ break;
+ case View.LAYOUT_DIRECTION_LTR:
+ default:
+ leftMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ?
+ startMargin : DEFAULT_MARGIN_RESOLVED;
+ rightMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ?
+ endMargin : DEFAULT_MARGIN_RESOLVED;
+ break;
+ }
}
+ mMarginFlags &= ~NEED_RESOLUTION_MASK;
}
/**
* @hide
*/
public boolean isLayoutRtl() {
- return (layoutDirection == View.LAYOUT_DIRECTION_RTL);
+ return ((mMarginFlags & LAYOUT_DIRECTION_MASK) == View.LAYOUT_DIRECTION_RTL);
}
/**
* @hide
*/
@Override
- public void onDebugDraw(View view, Canvas canvas) {
- drawRect(canvas,
- view.getLeft() - leftMargin,
- view.getTop() - topMargin,
- view.getRight() + rightMargin,
- view.getBottom() + bottomMargin, Color.MAGENTA);
+ public void onDebugDraw(View view, Canvas canvas, Paint paint) {
+ Insets oi = isLayoutModeOptical(view.mParent) ? view.getOpticalInsets() : Insets.NONE;
+
+ fillDifference(canvas,
+ view.getLeft() + oi.left,
+ view.getTop() + oi.top,
+ view.getRight() - oi.right,
+ view.getBottom() - oi.bottom,
+ leftMargin,
+ topMargin,
+ rightMargin,
+ bottomMargin,
+ paint);
}
}
@@ -6119,50 +6446,25 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private static final int MAX_POOL_SIZE = 32;
- private static final Object sPoolLock = new Object();
-
- private static ChildListForAccessibility sPool;
-
- private static int sPoolSize;
-
- private boolean mIsPooled;
-
- private ChildListForAccessibility mNext;
+ private static final SynchronizedPool<ChildListForAccessibility> sPool =
+ new SynchronizedPool<ChildListForAccessibility>(MAX_POOL_SIZE);
private final ArrayList<View> mChildren = new ArrayList<View>();
private final ArrayList<ViewLocationHolder> mHolders = new ArrayList<ViewLocationHolder>();
public static ChildListForAccessibility obtain(ViewGroup parent, boolean sort) {
- ChildListForAccessibility list = null;
- synchronized (sPoolLock) {
- if (sPool != null) {
- list = sPool;
- sPool = list.mNext;
- list.mNext = null;
- list.mIsPooled = false;
- sPoolSize--;
- } else {
- list = new ChildListForAccessibility();
- }
- list.init(parent, sort);
- return list;
+ ChildListForAccessibility list = sPool.acquire();
+ if (list == null) {
+ list = new ChildListForAccessibility();
}
+ list.init(parent, sort);
+ return list;
}
public void recycle() {
- if (mIsPooled) {
- throw new IllegalStateException("Instance already recycled.");
- }
clear();
- synchronized (sPoolLock) {
- if (sPoolSize < MAX_POOL_SIZE) {
- mNext = sPool;
- mIsPooled = true;
- sPool = this;
- sPoolSize++;
- }
- }
+ sPool.release(this);
}
public int getChildCount() {
@@ -6216,15 +6518,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private static final int MAX_POOL_SIZE = 32;
- private static final Object sPoolLock = new Object();
-
- private static ViewLocationHolder sPool;
-
- private static int sPoolSize;
-
- private boolean mIsPooled;
-
- private ViewLocationHolder mNext;
+ private static final SynchronizedPool<ViewLocationHolder> sPool =
+ new SynchronizedPool<ViewLocationHolder>(MAX_POOL_SIZE);
private final Rect mLocation = new Rect();
@@ -6233,35 +6528,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private int mLayoutDirection;
public static ViewLocationHolder obtain(ViewGroup root, View view) {
- ViewLocationHolder holder = null;
- synchronized (sPoolLock) {
- if (sPool != null) {
- holder = sPool;
- sPool = holder.mNext;
- holder.mNext = null;
- holder.mIsPooled = false;
- sPoolSize--;
- } else {
- holder = new ViewLocationHolder();
- }
- holder.init(root, view);
- return holder;
+ ViewLocationHolder holder = sPool.acquire();
+ if (holder == null) {
+ holder = new ViewLocationHolder();
}
+ holder.init(root, view);
+ return holder;
}
public void recycle() {
- if (mIsPooled) {
- throw new IllegalStateException("Instance already recycled.");
- }
clear();
- synchronized (sPoolLock) {
- if (sPoolSize < MAX_POOL_SIZE) {
- mNext = sPool;
- mIsPooled = true;
- sPool = this;
- sPoolSize++;
- }
- }
+ sPool.release(this);
}
@Override
@@ -6337,14 +6614,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return sDebugPaint;
}
- private static float[] getDebugLines(int x1, int y1, int x2, int y2) {
+ private void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
if (sDebugLines== null) {
sDebugLines = new float[16];
}
- x2--;
- y2--;
-
sDebugLines[0] = x1;
sDebugLines[1] = y1;
sDebugLines[2] = x2;
@@ -6353,18 +6627,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
sDebugLines[4] = x2;
sDebugLines[5] = y1;
sDebugLines[6] = x2;
- sDebugLines[7] = y2 + 1;
+ sDebugLines[7] = y2;
- sDebugLines[8] = x2 + 1;
+ sDebugLines[8] = x2;
sDebugLines[9] = y2;
sDebugLines[10] = x1;
sDebugLines[11] = y2;
- sDebugLines[12] = x1;
- sDebugLines[13] = y2;
+ sDebugLines[12] = x1;
+ sDebugLines[13] = y2;
sDebugLines[14] = x1;
sDebugLines[15] = y1;
- return sDebugLines;
+ canvas.drawLines(sDebugLines, paint);
}
}
diff --git a/core/java/android/view/ViewGroupOverlay.java b/core/java/android/view/ViewGroupOverlay.java
new file mode 100644
index 0000000..16afc5d
--- /dev/null
+++ b/core/java/android/view/ViewGroupOverlay.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+/**
+ * A group overlay is an extra layer that sits on top of a ViewGroup
+ * (the "host view") which is drawn after all other content in that view
+ * (including the view group's children). Interaction with the overlay
+ * layer is done by adding and removing views and drawables.
+ *
+ * <p>ViewGroupOverlay is a subclass of {@link ViewOverlay}, adding the ability to
+ * manage views for overlays on ViewGroups, in addition to the drawable
+ * support in ViewOverlay.</p>
+ *
+ * @see ViewGroup#getOverlay()
+ */
+public class ViewGroupOverlay extends ViewOverlay {
+
+ ViewGroupOverlay(Context context, View hostView) {
+ super(context, hostView);
+ }
+
+ /**
+ * Adds a View to the overlay. The bounds of the added view should be
+ * relative to the host view. Any view added to the overlay should be
+ * removed when it is no longer needed or no longer visible.
+ *
+ * <p>Views in the overlay are visual-only; they do not receive input
+ * events and do not participate in focus traversal. Overlay views
+ * are intended to be transient, such as might be needed by a temporary
+ * animation effect.</p>
+ *
+ * <p>If the view has a parent, the view will be removed from that parent
+ * before being added to the overlay. Also, if that parent is attached
+ * in the current view hierarchy, the view will be repositioned
+ * such that it is in the same relative location inside the activity. For
+ * example, if the view's current parent lies 100 pixels to the right
+ * and 200 pixels down from the origin of the overlay's
+ * host view, then the view will be offset by (100, 200).</p>
+ *
+ * @param view The View to be added to the overlay. The added view will be
+ * drawn when the overlay is drawn.
+ * @see #remove(View)
+ * @see ViewOverlay#add(Drawable)
+ */
+ public void add(View view) {
+ mOverlayViewGroup.add(view);
+ }
+
+ /**
+ * Removes the specified View from the overlay.
+ *
+ * @param view The View to be removed from the overlay.
+ * @see #add(View)
+ * @see ViewOverlay#remove(Drawable)
+ */
+ public void remove(View view) {
+ mOverlayViewGroup.remove(view);
+ }
+}
diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java
new file mode 100644
index 0000000..5510939
--- /dev/null
+++ b/core/java/android/view/ViewOverlay.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import java.util.ArrayList;
+
+/**
+ * An overlay is an extra layer that sits on top of a View (the "host view")
+ * which is drawn after all other content in that view (including children,
+ * if the view is a ViewGroup). Interaction with the overlay layer is done
+ * by adding and removing drawables.
+ *
+ * <p>An overlay requested from a ViewGroup is of type {@link ViewGroupOverlay},
+ * which also supports adding and removing views.</p>
+ *
+ * @see View#getOverlay() View.getOverlay()
+ * @see ViewGroup#getOverlay() ViewGroup.getOverlay()
+ * @see ViewGroupOverlay
+ */
+public class ViewOverlay {
+
+ /**
+ * The actual container for the drawables (and views, if it's a ViewGroupOverlay).
+ * All of the management and rendering details for the overlay are handled in
+ * OverlayViewGroup.
+ */
+ OverlayViewGroup mOverlayViewGroup;
+
+ ViewOverlay(Context context, View hostView) {
+ mOverlayViewGroup = new OverlayViewGroup(context, hostView);
+ }
+
+ /**
+ * Used internally by View and ViewGroup to handle drawing and invalidation
+ * of the overlay
+ * @return
+ */
+ ViewGroup getOverlayView() {
+ return mOverlayViewGroup;
+ }
+
+ /**
+ * Adds a Drawable to the overlay. The bounds of the drawable should be relative to
+ * the host view. Any drawable added to the overlay should be removed when it is no longer
+ * needed or no longer visible.
+ *
+ * @param drawable The Drawable to be added to the overlay. This drawable will be
+ * drawn when the view redraws its overlay.
+ * @see #remove(Drawable)
+ */
+ public void add(Drawable drawable) {
+ mOverlayViewGroup.add(drawable);
+ }
+
+ /**
+ * Removes the specified Drawable from the overlay.
+ *
+ * @param drawable The Drawable to be removed from the overlay.
+ * @see #add(Drawable)
+ */
+ public void remove(Drawable drawable) {
+ mOverlayViewGroup.remove(drawable);
+ }
+
+ /**
+ * Removes all content from the overlay.
+ */
+ public void clear() {
+ mOverlayViewGroup.clear();
+ }
+
+ boolean isEmpty() {
+ return mOverlayViewGroup.isEmpty();
+ }
+
+ /**
+ * OverlayViewGroup is a container that View and ViewGroup use to host
+ * drawables and views added to their overlays ({@link ViewOverlay} and
+ * {@link ViewGroupOverlay}, respectively). Drawables are added to the overlay
+ * via the add/remove methods in ViewOverlay, Views are added/removed via
+ * ViewGroupOverlay. These drawable and view objects are
+ * drawn whenever the view itself is drawn; first the view draws its own
+ * content (and children, if it is a ViewGroup), then it draws its overlay
+ * (if it has one).
+ *
+ * <p>Besides managing and drawing the list of drawables, this class serves
+ * two purposes:
+ * (1) it noops layout calls because children are absolutely positioned and
+ * (2) it forwards all invalidation calls to its host view. The invalidation
+ * redirect is necessary because the overlay is not a child of the host view
+ * and invalidation cannot therefore follow the normal path up through the
+ * parent hierarchy.</p>
+ *
+ * @see View#getOverlay()
+ * @see ViewGroup#getOverlay()
+ */
+ static class OverlayViewGroup extends ViewGroup {
+
+ /**
+ * The View for which this is an overlay. Invalidations of the overlay are redirected to
+ * this host view.
+ */
+ View mHostView;
+
+ /**
+ * The set of drawables to draw when the overlay is rendered.
+ */
+ ArrayList<Drawable> mDrawables = null;
+
+ OverlayViewGroup(Context context, View hostView) {
+ super(context);
+ mHostView = hostView;
+ mAttachInfo = mHostView.mAttachInfo;
+ mRight = hostView.getWidth();
+ mBottom = hostView.getHeight();
+ }
+
+ public void add(Drawable drawable) {
+ if (mDrawables == null) {
+
+ mDrawables = new ArrayList<Drawable>();
+ }
+ if (!mDrawables.contains(drawable)) {
+ // Make each drawable unique in the overlay; can't add it more than once
+ mDrawables.add(drawable);
+ invalidate(drawable.getBounds());
+ drawable.setCallback(this);
+ }
+ }
+
+ public void remove(Drawable drawable) {
+ if (mDrawables != null) {
+ mDrawables.remove(drawable);
+ invalidate(drawable.getBounds());
+ drawable.setCallback(null);
+ }
+ }
+
+ public void add(View child) {
+ if (child.getParent() instanceof ViewGroup) {
+ ViewGroup parent = (ViewGroup) child.getParent();
+ if (parent != mHostView && parent.getParent() != null &&
+ parent.mAttachInfo != null) {
+ // Moving to different container; figure out how to position child such that
+ // it is in the same location on the screen
+ int[] parentLocation = new int[2];
+ int[] hostViewLocation = new int[2];
+ parent.getLocationOnScreen(parentLocation);
+ mHostView.getLocationOnScreen(hostViewLocation);
+ child.offsetLeftAndRight(parentLocation[0] - hostViewLocation[0]);
+ child.offsetTopAndBottom(parentLocation[1] - hostViewLocation[1]);
+ }
+ parent.removeView(child);
+ }
+ super.addView(child);
+ }
+
+ public void remove(View view) {
+ super.removeView(view);
+ }
+
+ public void clear() {
+ removeAllViews();
+ if (mDrawables != null) {
+ mDrawables.clear();
+ }
+ }
+
+ boolean isEmpty() {
+ if (getChildCount() == 0 &&
+ (mDrawables == null || mDrawables.size() == 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void invalidateDrawable(Drawable drawable) {
+ invalidate(drawable.getBounds());
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ super.dispatchDraw(canvas);
+ final int numDrawables = (mDrawables == null) ? 0 : mDrawables.size();
+ for (int i = 0; i < numDrawables; ++i) {
+ mDrawables.get(i).draw(canvas);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ // Noop: children are positioned absolutely
+ }
+
+ /*
+ The following invalidation overrides exist for the purpose of redirecting invalidation to
+ the host view. The overlay is not parented to the host view (since a View cannot be a
+ parent), so the invalidation cannot proceed through the normal parent hierarchy.
+ There is a built-in assumption that the overlay exactly covers the host view, therefore
+ the invalidation rectangles received do not need to be adjusted when forwarded to
+ the host view.
+ */
+
+ @Override
+ public void invalidate(Rect dirty) {
+ super.invalidate(dirty);
+ if (mHostView != null) {
+ mHostView.invalidate(dirty);
+ }
+ }
+
+ @Override
+ public void invalidate(int l, int t, int r, int b) {
+ super.invalidate(l, t, r, b);
+ if (mHostView != null) {
+ mHostView.invalidate(l, t, r, b);
+ }
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ if (mHostView != null) {
+ mHostView.invalidate();
+ }
+ }
+
+ @Override
+ void invalidate(boolean invalidateCache) {
+ super.invalidate(invalidateCache);
+ if (mHostView != null) {
+ mHostView.invalidate(invalidateCache);
+ }
+ }
+
+ @Override
+ void invalidateViewProperty(boolean invalidateParent, boolean forceRedraw) {
+ super.invalidateViewProperty(invalidateParent, forceRedraw);
+ if (mHostView != null) {
+ mHostView.invalidateViewProperty(invalidateParent, forceRedraw);
+ }
+ }
+
+ @Override
+ protected void invalidateParentCaches() {
+ super.invalidateParentCaches();
+ if (mHostView != null) {
+ mHostView.invalidateParentCaches();
+ }
+ }
+
+ @Override
+ protected void invalidateParentIfNeeded() {
+ super.invalidateParentIfNeeded();
+ if (mHostView != null) {
+ mHostView.invalidateParentIfNeeded();
+ }
+ }
+
+ public void invalidateChildFast(View child, final Rect dirty) {
+ if (mHostView != null) {
+ // Note: This is not a "fast" invalidation. Would be nice to instead invalidate
+ // using DisplayList properties and a dirty rect instead of causing a real
+ // invalidation of the host view
+ int left = child.mLeft;
+ int top = child.mTop;
+ if (!child.getMatrix().isIdentity()) {
+ child.transformRect(dirty);
+ }
+ dirty.offset(left, top);
+ mHostView.invalidate(dirty);
+ }
+ }
+
+ @Override
+ public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
+ if (mHostView != null) {
+ dirty.offset(location[0], location[1]);
+ if (mHostView instanceof ViewGroup) {
+ location[0] = 0;
+ location[1] = 0;
+ super.invalidateChildInParent(location, dirty);
+ return ((ViewGroup) mHostView).invalidateChildInParent(location, dirty);
+ } else {
+ invalidate(dirty);
+ }
+ }
+ return null;
+ }
+ }
+
+}
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index ddff91d..d79aa7e 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -146,9 +146,13 @@ public interface ViewParent {
public View focusSearch(View v, int direction);
/**
- * Change the z order of the child so it's on top of all other children
+ * Change the z order of the child so it's on top of all other children.
+ * This ordering change may affect layout, if this container
+ * uses an order-dependent layout scheme (e.g., LinearLayout). This
+ * method should be followed by calls to {@link #requestLayout()} and
+ * {@link View#invalidate()} on this parent.
*
- * @param child
+ * @param child The child to bring to the top of the z order
*/
public void bringChildToFront(View child);
@@ -295,4 +299,105 @@ public interface ViewParent {
* @hide
*/
public void childAccessibilityStateChanged(View child);
+
+ /**
+ * Tells if this view parent can resolve the layout direction.
+ * See {@link View#setLayoutDirection(int)}
+ *
+ * @return True if this view parent can resolve the layout direction.
+ *
+ * @hide
+ */
+ public boolean canResolveLayoutDirection();
+
+ /**
+ * Tells if this view parent layout direction is resolved.
+ * See {@link View#setLayoutDirection(int)}
+ *
+ * @return True if this view parent layout direction is resolved.
+ *
+ * @hide
+ */
+ public boolean isLayoutDirectionResolved();
+
+ /**
+ * Return this view parent layout direction. See {@link View#getLayoutDirection()}
+ *
+ * @return {@link View#LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns
+ * {@link View#LAYOUT_DIRECTION_LTR} if the layout direction is not RTL.
+ *
+ * @hide
+ */
+ public int getLayoutDirection();
+
+ /**
+ * Tells if this view parent can resolve the text direction.
+ * See {@link View#setTextDirection(int)}
+ *
+ * @return True if this view parent can resolve the text direction.
+ *
+ * @hide
+ */
+ public boolean canResolveTextDirection();
+
+ /**
+ * Tells if this view parent text direction is resolved.
+ * See {@link View#setTextDirection(int)}
+ *
+ * @return True if this view parent text direction is resolved.
+ *
+ * @hide
+ */
+ public boolean isTextDirectionResolved();
+
+ /**
+ * Return this view parent text direction. See {@link View#getTextDirection()}
+ *
+ * @return the resolved text direction. Returns one of:
+ *
+ * {@link View#TEXT_DIRECTION_FIRST_STRONG}
+ * {@link View#TEXT_DIRECTION_ANY_RTL},
+ * {@link View#TEXT_DIRECTION_LTR},
+ * {@link View#TEXT_DIRECTION_RTL},
+ * {@link View#TEXT_DIRECTION_LOCALE}
+ *
+ * @hide
+ */
+ public int getTextDirection();
+
+ /**
+ * Tells if this view parent can resolve the text alignment.
+ * See {@link View#setTextAlignment(int)}
+ *
+ * @return True if this view parent can resolve the text alignment.
+ *
+ * @hide
+ */
+ public boolean canResolveTextAlignment();
+
+ /**
+ * Tells if this view parent text alignment is resolved.
+ * See {@link View#setTextAlignment(int)}
+ *
+ * @return True if this view parent text alignment is resolved.
+ *
+ * @hide
+ */
+ public boolean isTextAlignmentResolved();
+
+ /**
+ * Return this view parent text alignment. See {@link android.view.View#getTextAlignment()}
+ *
+ * @return the resolved text alignment. Returns one of:
+ *
+ * {@link View#TEXT_ALIGNMENT_GRAVITY},
+ * {@link View#TEXT_ALIGNMENT_CENTER},
+ * {@link View#TEXT_ALIGNMENT_TEXT_START},
+ * {@link View#TEXT_ALIGNMENT_TEXT_END},
+ * {@link View#TEXT_ALIGNMENT_VIEW_START},
+ * {@link View#TEXT_ALIGNMENT_VIEW_END}
+ *
+ * @hide
+ */
+ public int getTextAlignment();
}
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 22f98b7..71a85bc 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -323,6 +323,15 @@ public class ViewPropertyAnimator {
}
/**
+ * Returns the timing interpolator that this animation uses.
+ *
+ * @return The timing interpolator for this animation.
+ */
+ public TimeInterpolator getInterpolator() {
+ return null;
+ }
+
+ /**
* Sets a listener for events in the underlying Animators that run the property
* animations.
*
@@ -829,7 +838,7 @@ public class ViewPropertyAnimator {
NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue);
mPendingAnimations.add(nameValuePair);
mView.removeCallbacks(mAnimationStarter);
- mView.post(mAnimationStarter);
+ mView.postOnAnimation(mAnimationStarter);
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3b91e00..6b2ed91 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -42,7 +42,6 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
-import android.os.LatencyTimer;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
@@ -107,6 +106,7 @@ public final class ViewRootImpl implements ViewParent,
private static final boolean DEBUG_IMF = false || LOCAL_LOGV;
private static final boolean DEBUG_CONFIGURATION = false || LOCAL_LOGV;
private static final boolean DEBUG_FPS = false;
+ private static final boolean DEBUG_INPUT_PROCESSING = false || LOCAL_LOGV;
private static final boolean USE_RENDER_THREAD = false;
@@ -114,10 +114,7 @@ public final class ViewRootImpl implements ViewParent,
* Set this system property to true to force the view hierarchy to render
* at 60 Hz. This can be used to measure the potential framerate.
*/
- private static final String PROPERTY_PROFILE_RENDERING = "viewancestor.profile_rendering";
-
- private static final boolean MEASURE_LATENCY = false;
- private static LatencyTimer lt;
+ private static final String PROPERTY_PROFILE_RENDERING = "viewancestor.profile_rendering";
/**
* Maximum time we allow the user to roll the trackball enough to generate
@@ -137,25 +134,15 @@ public final class ViewRootImpl implements ViewParent,
private static boolean sRenderThreadQueried = false;
private static final Object[] sRenderThreadQueryLock = new Object[0];
+ final Context mContext;
final IWindowSession mWindowSession;
final Display mDisplay;
-
- long mLastTrackballTime = 0;
- final TrackballAxis mTrackballAxisX = new TrackballAxis();
- final TrackballAxis mTrackballAxisY = new TrackballAxis();
-
- final SimulatedDpad mSimulatedDpad;
-
- int mLastJoystickXDirection;
- int mLastJoystickYDirection;
- int mLastJoystickXKeyCode;
- int mLastJoystickYKeyCode;
+ final String mBasePackageName;
final int[] mTmpLocation = new int[2];
final TypedValue mTmpValue = new TypedValue();
-
- final InputMethodCallback mInputMethodCallback;
+
final Thread mThread;
final WindowLeaked mLocation;
@@ -169,9 +156,6 @@ public final class ViewRootImpl implements ViewParent,
int mSeq;
View mView;
- View mFocusedView;
- View mRealFocusedView; // this is not set to null in touch mode
- View mOldFocusedView;
View mAccessibilityFocusedHost;
AccessibilityNodeInfo mAccessibilityFocusedVirtualView;
@@ -198,7 +182,6 @@ public final class ViewRootImpl implements ViewParent,
int mHeight;
Rect mDirty;
final Rect mCurrentDirty = new Rect();
- final Rect mPreviousDirty = new Rect();
boolean mIsAnimating;
CompatibilityInfo.Translator mTranslator;
@@ -228,26 +211,29 @@ public final class ViewRootImpl implements ViewParent,
boolean mHasHadWindowFocus;
boolean mLastWasImTarget;
boolean mWindowsAnimating;
+ boolean mDrawDuringWindowsAnimating;
boolean mIsDrawing;
int mLastSystemUiVisibility;
int mClientWindowLayoutFlags;
-
- /** @hide */
- public static final int EVENT_NOT_HANDLED = 0;
- /** @hide */
- public static final int EVENT_HANDLED = 1;
- /** @hide */
- public static final int EVENT_IN_PROGRESS = 2;
+ boolean mLastOverscanRequested;
// Pool of queued input events.
private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10;
private QueuedInputEvent mQueuedInputEventPool;
private int mQueuedInputEventPoolSize;
- // Input event queue.
- QueuedInputEvent mFirstPendingInputEvent;
- QueuedInputEvent mCurrentInputEvent;
+ /* Input event queue.
+ * Pending input events are input events waiting to be delivered to the input stages
+ * and handled by the application.
+ */
+ QueuedInputEvent mPendingInputEventHead;
+ QueuedInputEvent mPendingInputEventTail;
+ int mPendingInputEventCount;
boolean mProcessInputEventsScheduled;
+ String mPendingInputEventQueueLengthCounterName = "pq";
+
+ InputStage mFirstInputStage;
+ InputStage mFirstPostImeInputStage;
boolean mWindowAttributesChanged = false;
int mWindowAttributesChangesFlag = 0;
@@ -264,6 +250,7 @@ public final class ViewRootImpl implements ViewParent,
// These are accessed by multiple threads.
final Rect mWinFrame; // frame given by window manager.
+ final Rect mPendingOverscanInsets = new Rect();
final Rect mPendingVisibleInsets = new Rect();
final Rect mPendingContentInsets = new Rect();
final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
@@ -276,7 +263,7 @@ public final class ViewRootImpl implements ViewParent,
boolean mScrollMayChange;
int mSoftInputMode;
- View mLastScrolledFocus;
+ WeakReference<View> mLastScrolledFocus;
int mScrollY;
int mCurScrollY;
Scroller mScroller;
@@ -296,15 +283,15 @@ public final class ViewRootImpl implements ViewParent,
final PointF mLastTouchPoint = new PointF();
private boolean mProfileRendering;
- private Thread mRenderProfiler;
- private volatile boolean mRenderProfilingEnabled;
+ private Choreographer.FrameCallback mRenderProfiler;
+ private boolean mRenderProfilingEnabled;
// Variables to track frames per second, enabled via DEBUG_FPS flag
private long mFpsStartTime = -1;
private long mFpsPrevTime = -1;
private int mFpsNumFrames;
- private final ArrayList<DisplayList> mDisplayLists = new ArrayList<DisplayList>(24);
+ private final ArrayList<DisplayList> mDisplayLists = new ArrayList<DisplayList>();
/**
* see {@link #playSoundEffect(int)}
@@ -324,6 +311,10 @@ public final class ViewRootImpl implements ViewParent,
private final int mDensity;
private final int mNoncompatDensity;
+ private boolean mInLayout = false;
+ ArrayList<View> mLayoutRequesters = new ArrayList<View>();
+ boolean mHandlingLayoutInLayoutRequest = false;
+
private int mViewLayoutDirectionInitial;
/**
@@ -341,19 +332,10 @@ public final class ViewRootImpl implements ViewParent,
}
public ViewRootImpl(Context context, Display display) {
- super();
-
- if (MEASURE_LATENCY) {
- if (lt == null) {
- lt = new LatencyTimer(100, 1000);
- }
- }
-
- // 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.
- mWindowSession = WindowManagerGlobal.getWindowSession(context.getMainLooper());
+ mContext = context;
+ mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
+ mBasePackageName = context.getBasePackageName();
CompatibilityInfoHolder cih = display.getCompatibilityInfo();
mCompatibilityInfo = cih != null ? cih : new CompatibilityInfoHolder();
@@ -369,7 +351,6 @@ public final class ViewRootImpl implements ViewParent,
mWinFrame = new Rect();
mWindow = new W(this);
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
- mInputMethodCallback = new InputMethodCallback(this);
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region();
@@ -385,14 +366,11 @@ public final class ViewRootImpl implements ViewParent,
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
- mProfileRendering = Boolean.parseBoolean(
- SystemProperties.get(PROPERTY_PROFILE_RENDERING, "false"));
mChoreographer = Choreographer.getInstance();
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mAttachInfo.mScreenOn = powerManager.isScreenOn();
loadSystemProperties();
- mSimulatedDpad = new SimulatedDpad(context);
}
/**
@@ -478,6 +456,9 @@ public final class ViewRootImpl implements ViewParent,
mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);
+ if (mWindowAttributes.packageName == null) {
+ mWindowAttributes.packageName = mBasePackageName;
+ }
attrs = mWindowAttributes;
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
@@ -562,6 +543,7 @@ public final class ViewRootImpl implements ViewParent,
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
}
+ mPendingOverscanInsets.set(0, 0, 0, 0);
mPendingContentInsets.set(mAttachInfo.mContentInsets);
mPendingVisibleInsets.set(0, 0, 0, 0);
if (DEBUG_LAYOUT) Log.v(TAG, "Added window " + mWindow);
@@ -616,12 +598,11 @@ public final class ViewRootImpl implements ViewParent,
}
if (mInputChannel != null) {
if (mInputQueueCallback != null) {
- mInputQueue = new InputQueue(mInputChannel);
+ mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
- } else {
- mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
- Looper.myLooper());
}
+ mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
+ Looper.myLooper());
}
view.assignParent(this);
@@ -635,6 +616,23 @@ public final class ViewRootImpl implements ViewParent,
if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
+
+ // Set up the input pipeline.
+ CharSequence counterSuffix = attrs.getTitle();
+ InputStage syntheticStage = new SyntheticInputStage();
+ InputStage viewPostImeStage = new ViewPostImeInputStage(syntheticStage);
+ InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
+ "aq:native-post-ime:" + counterSuffix);
+ InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
+ InputStage imeStage = new ImeInputStage(earlyPostImeStage,
+ "aq:ime:" + counterSuffix);
+ InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
+ InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
+ "aq:native-pre-ime:" + counterSuffix);
+
+ mFirstInputStage = nativePreImeStage;
+ mFirstPostImeInputStage = earlyPostImeStage;
+ mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
}
}
}
@@ -740,9 +738,11 @@ public final class ViewRootImpl implements ViewParent,
final boolean translucent = attrs.format != PixelFormat.OPAQUE;
mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent);
- mAttachInfo.mHardwareAccelerated = mAttachInfo.mHardwareAccelerationRequested
- = mAttachInfo.mHardwareRenderer != null;
-
+ if (mAttachInfo.mHardwareRenderer != null) {
+ mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
+ mAttachInfo.mHardwareAccelerated =
+ mAttachInfo.mHardwareAccelerationRequested = true;
+ }
} else if (fakeHwAccelerated) {
// The window had wanted to use hardware acceleration, but this
// is not allowed in its process. By setting this flag, it can
@@ -774,6 +774,9 @@ public final class ViewRootImpl implements ViewParent,
attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
+ if (mWindowAttributes.packageName == null) {
+ mWindowAttributes.packageName = mBasePackageName;
+ }
mWindowAttributes.flags |= compatibleWindowFlag;
applyKeepScreenOnFlag(mWindowAttributes);
@@ -830,9 +833,11 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void requestLayout() {
- checkThread();
- mLayoutRequested = true;
- scheduleTraversals();
+ if (!mHandlingLayoutInLayoutRequest) {
+ checkThread();
+ mLayoutRequested = true;
+ scheduleTraversals();
+ }
}
@Override
@@ -1200,6 +1205,7 @@ public final class ViewRootImpl implements ViewParent,
host.setLayoutDirection(mLastConfiguration.getLayoutDirection());
}
host.dispatchAttachedToWindow(attachInfo, 0);
+ attachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets);
host.fitSystemWindows(mFitSystemWindowsInsets);
//Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn);
@@ -1245,6 +1251,9 @@ public final class ViewRootImpl implements ViewParent,
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
+ if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
+ insetsChanged = true;
+ }
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
insetsChanged = true;
}
@@ -1310,15 +1319,20 @@ public final class ViewRootImpl implements ViewParent,
}
}
- if (params != null && (host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
- if (!PixelFormat.formatHasAlpha(params.format)) {
- params.format = PixelFormat.TRANSLUCENT;
+ if (params != null) {
+ if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
+ if (!PixelFormat.formatHasAlpha(params.format)) {
+ params.format = PixelFormat.TRANSLUCENT;
+ }
}
+ mAttachInfo.mOverscanRequested = (params.flags
+ & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0;
}
if (mFitSystemWindowsRequested) {
mFitSystemWindowsRequested = false;
mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets);
+ mLastOverscanRequested = mAttachInfo.mOverscanRequested;
host.fitSystemWindows(mFitSystemWindowsInsets);
if (mLayoutRequested) {
// Short-circuit catching a new layout request here, so
@@ -1373,7 +1387,6 @@ public final class ViewRootImpl implements ViewParent,
boolean hwInitialized = false;
boolean contentInsetsChanged = false;
- boolean visibleInsetsChanged;
boolean hadSurface = mSurface.isValid();
try {
@@ -1384,8 +1397,13 @@ public final class ViewRootImpl implements ViewParent,
final int surfaceGenerationId = mSurface.getGenerationId();
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
+ if (!mDrawDuringWindowsAnimating) {
+ mWindowsAnimating |=
+ (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0;
+ }
if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString()
+ + " overscan=" + mPendingOverscanInsets.toShortString()
+ " content=" + mPendingContentInsets.toShortString()
+ " visible=" + mPendingVisibleInsets.toShortString()
+ " surface=" + mSurface);
@@ -1397,9 +1415,11 @@ public final class ViewRootImpl implements ViewParent,
mPendingConfiguration.seq = 0;
}
+ final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
+ mAttachInfo.mOverscanInsets);
contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
- visibleInsetsChanged = !mPendingVisibleInsets.equals(
+ final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
mAttachInfo.mVisibleInsets);
if (contentInsetsChanged) {
if (mWidth > 0 && mHeight > 0 && lp != null &&
@@ -1427,8 +1447,6 @@ public final class ViewRootImpl implements ViewParent,
}
// TODO: should handle create/resize failure
layerCanvas = mResizeBuffer.start(hwRendererCanvas);
- layerCanvas.setViewport(mWidth, mHeight);
- layerCanvas.onPreDraw(null);
final int restoreCount = layerCanvas.save();
int yoff;
@@ -1465,9 +1483,6 @@ public final class ViewRootImpl implements ViewParent,
} catch (OutOfMemoryError e) {
Log.w(TAG, "Not enough memory for content change anim buffer", e);
} finally {
- if (layerCanvas != null) {
- layerCanvas.onPostDraw();
- }
if (mResizeBuffer != null) {
mResizeBuffer.end(hwRendererCanvas);
if (!completed) {
@@ -1481,9 +1496,18 @@ public final class ViewRootImpl implements ViewParent,
if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
+ mAttachInfo.mContentInsets);
}
+ if (overscanInsetsChanged) {
+ mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);
+ if (DEBUG_LAYOUT) Log.v(TAG, "Overscan insets changing to: "
+ + mAttachInfo.mOverscanInsets);
+ // Need to relayout with content insets.
+ contentInsetsChanged = true;
+ }
if (contentInsetsChanged || mLastSystemUiVisibility !=
- mAttachInfo.mSystemUiVisibility || mFitSystemWindowsRequested) {
+ mAttachInfo.mSystemUiVisibility || mFitSystemWindowsRequested
+ || mLastOverscanRequested != mAttachInfo.mOverscanRequested) {
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
+ mLastOverscanRequested = mAttachInfo.mOverscanRequested;
mFitSystemWindowsRequested = false;
mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets);
host.fitSystemWindows(mFitSystemWindowsInsets);
@@ -1512,16 +1536,7 @@ public final class ViewRootImpl implements ViewParent,
hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
mHolder.getSurface());
} catch (Surface.OutOfResourcesException e) {
- Log.e(TAG, "OutOfResourcesException initializing HW surface", e);
- try {
- if (!mWindowSession.outOfMemory(mWindow) &&
- Process.myUid() != Process.SYSTEM_UID) {
- Slog.w(TAG, "No processes killed for memory; killing self");
- Process.killProcess(Process.myPid());
- }
- } catch (RemoteException ex) {
- }
- mLayoutRequested = true; // ask wm for a new surface next time.
+ handleOutOfResourcesException(e);
return;
}
}
@@ -1529,7 +1544,9 @@ public final class ViewRootImpl implements ViewParent,
} else if (!mSurface.isValid()) {
// If the surface has been removed, then reset the scroll
// positions.
- mLastScrolledFocus = null;
+ if (mLastScrolledFocus != null) {
+ mLastScrolledFocus.clear();
+ }
mScrollY = mCurScrollY = 0;
if (mScroller != null) {
mScroller.abortAnimation();
@@ -1546,15 +1563,7 @@ public final class ViewRootImpl implements ViewParent,
try {
mAttachInfo.mHardwareRenderer.updateSurface(mHolder.getSurface());
} catch (Surface.OutOfResourcesException e) {
- Log.e(TAG, "OutOfResourcesException updating HW surface", e);
- try {
- if (!mWindowSession.outOfMemory(mWindow)) {
- Slog.w(TAG, "No processes killed for memory; killing self");
- Process.killProcess(Process.myPid());
- }
- } catch (RemoteException ex) {
- }
- mLayoutRequested = true; // ask wm for a new surface next time.
+ handleOutOfResourcesException(e);
return;
}
}
@@ -1630,7 +1639,7 @@ public final class ViewRootImpl implements ViewParent,
if (mAttachInfo.mHardwareRenderer != null &&
mAttachInfo.mHardwareRenderer.isEnabled()) {
- if (hwInitialized || windowShouldResize ||
+ if (hwInitialized ||
mWidth != mAttachInfo.mHardwareRenderer.getWidth() ||
mHeight != mAttachInfo.mHardwareRenderer.getHeight()) {
mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight);
@@ -1718,9 +1727,9 @@ public final class ViewRootImpl implements ViewParent,
boolean triggerGlobalLayoutListener = didLayout
|| attachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
- performLayout();
+ performLayout(lp, desiredWindowWidth, desiredWindowHeight);
- // By this point all views have been sized and positionned
+ // By this point all views have been sized and positioned
// We can compute the transparent area
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
@@ -1738,6 +1747,7 @@ public final class ViewRootImpl implements ViewParent,
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
+ mFullRedrawNeeded = true;
// reconfigure window manager
try {
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
@@ -1805,13 +1815,11 @@ public final class ViewRootImpl implements ViewParent,
if (mView != null) {
if (!mView.hasFocus()) {
mView.requestFocus(View.FOCUS_FORWARD);
- mFocusedView = mRealFocusedView = mView.findFocus();
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: requested focused view="
- + mFocusedView);
+ + mView.findFocus());
} else {
- mRealFocusedView = mView.findFocus();
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: existing focused view="
- + mRealFocusedView);
+ + mView.findFocus());
}
}
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0) {
@@ -1878,6 +1886,19 @@ public final class ViewRootImpl implements ViewParent,
mIsInTraversal = false;
}
+ private void handleOutOfResourcesException(Surface.OutOfResourcesException e) {
+ Log.e(TAG, "OutOfResourcesException initializing HW surface", e);
+ try {
+ if (!mWindowSession.outOfMemory(mWindow) &&
+ Process.myUid() != Process.SYSTEM_UID) {
+ Slog.w(TAG, "No processes killed for memory; killing self");
+ Process.killProcess(Process.myPid());
+ }
+ } catch (RemoteException ex) {
+ }
+ mLayoutRequested = true; // ask wm for a new surface next time.
+ }
+
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
@@ -1887,9 +1908,62 @@ public final class ViewRootImpl implements ViewParent,
}
}
- private void performLayout() {
+ /**
+ * Called by {@link android.view.View#isInLayout()} to determine whether the view hierarchy
+ * is currently undergoing a layout pass.
+ *
+ * @return whether the view hierarchy is currently undergoing a layout pass
+ */
+ boolean isInLayout() {
+ return mInLayout;
+ }
+
+ /**
+ * Called by {@link android.view.View#requestLayout()} if the view hierarchy is currently
+ * undergoing a layout pass. requestLayout() should not generally be called during layout,
+ * unless the container hierarchy knows what it is doing (i.e., it is fine as long as
+ * all children in that container hierarchy are measured and laid out at the end of the layout
+ * pass for that container). If requestLayout() is called anyway, we handle it correctly
+ * by registering all requesters during a frame as it proceeds. At the end of the frame,
+ * we check all of those views to see if any still have pending layout requests, which
+ * indicates that they were not correctly handled by their container hierarchy. If that is
+ * the case, we clear all such flags in the tree, to remove the buggy flag state that leads
+ * to blank containers, and force a second request/measure/layout pass in this frame. If
+ * more requestLayout() calls are received during that second layout pass, we post those
+ * requests to the next frame to avoid possible infinite loops.
+ *
+ * <p>The return value from this method indicates whether the request should proceed
+ * (if it is a request during the first layout pass) or should be skipped and posted to the
+ * next frame (if it is a request during the second layout pass).</p>
+ *
+ * @param view the view that requested the layout.
+ *
+ * @return true if request should proceed, false otherwise.
+ */
+ boolean requestLayoutDuringLayout(final View view) {
+ if (view.mParent == null || view.mAttachInfo == null) {
+ // Would not normally trigger another layout, so just let it pass through as usual
+ return true;
+ }
+ if (!mLayoutRequesters.contains(view)) {
+ mLayoutRequesters.add(view);
+ }
+ if (!mHandlingLayoutInLayoutRequest) {
+ // Let the request proceed normally; it will be processed in a second layout pass
+ // if necessary
+ return true;
+ } else {
+ // Don't let the request proceed during the second layout pass.
+ // It will post to the next frame instead.
+ return false;
+ }
+ }
+
+ private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
+ int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
+ mInLayout = true;
final View host = mView;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
@@ -1900,9 +1974,125 @@ public final class ViewRootImpl implements ViewParent,
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
+
+ mInLayout = false;
+ int numViewsRequestingLayout = mLayoutRequesters.size();
+ if (numViewsRequestingLayout > 0) {
+ // requestLayout() was called during layout.
+ // If no layout-request flags are set on the requesting views, there is no problem.
+ // If some requests are still pending, then we need to clear those flags and do
+ // a full request/measure/layout pass to handle this situation.
+ ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
+ false);
+ if (validLayoutRequesters != null) {
+ // Set this flag to indicate that any further requests are happening during
+ // the second pass, which may result in posting those requests to the next
+ // frame instead
+ mHandlingLayoutInLayoutRequest = true;
+
+ // Process fresh layout requests, then measure and layout
+ int numValidRequests = validLayoutRequesters.size();
+ for (int i = 0; i < numValidRequests; ++i) {
+ final View view = validLayoutRequesters.get(i);
+ Log.w("View", "requestLayout() improperly called by " + view +
+ " during layout: running second layout pass");
+ view.requestLayout();
+ }
+ measureHierarchy(host, lp, mView.getContext().getResources(),
+ desiredWindowWidth, desiredWindowHeight);
+ mInLayout = true;
+ host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
+
+ mHandlingLayoutInLayoutRequest = false;
+
+ // Check the valid requests again, this time without checking/clearing the
+ // layout flags, since requests happening during the second pass get noop'd
+ validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
+ if (validLayoutRequesters != null) {
+ final ArrayList<View> finalRequesters = validLayoutRequesters;
+ // Post second-pass requests to the next frame
+ getRunQueue().post(new Runnable() {
+ @Override
+ public void run() {
+ int numValidRequests = finalRequesters.size();
+ for (int i = 0; i < numValidRequests; ++i) {
+ final View view = finalRequesters.get(i);
+ Log.w("View", "requestLayout() improperly called by " + view +
+ " during second layout pass: posting in next frame");
+ view.requestLayout();
+ }
+ }
+ });
+ }
+ }
+
+ }
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
+ mInLayout = false;
+ }
+
+ /**
+ * This method is called during layout when there have been calls to requestLayout() during
+ * layout. It walks through the list of views that requested layout to determine which ones
+ * still need it, based on visibility in the hierarchy and whether they have already been
+ * handled (as is usually the case with ListView children).
+ *
+ * @param layoutRequesters The list of views that requested layout during layout
+ * @param secondLayoutRequests Whether the requests were issued during the second layout pass.
+ * If so, the FORCE_LAYOUT flag was not set on requesters.
+ * @return A list of the actual views that still need to be laid out.
+ */
+ private ArrayList<View> getValidLayoutRequesters(ArrayList<View> layoutRequesters,
+ boolean secondLayoutRequests) {
+
+ int numViewsRequestingLayout = layoutRequesters.size();
+ ArrayList<View> validLayoutRequesters = null;
+ for (int i = 0; i < numViewsRequestingLayout; ++i) {
+ View view = layoutRequesters.get(i);
+ if (view != null && view.mAttachInfo != null && view.mParent != null &&
+ (secondLayoutRequests || (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) ==
+ View.PFLAG_FORCE_LAYOUT)) {
+ boolean gone = false;
+ View parent = view;
+ // Only trigger new requests for views in a non-GONE hierarchy
+ while (parent != null) {
+ if ((parent.mViewFlags & View.VISIBILITY_MASK) == View.GONE) {
+ gone = true;
+ break;
+ }
+ if (parent.mParent instanceof View) {
+ parent = (View) parent.mParent;
+ } else {
+ parent = null;
+ }
+ }
+ if (!gone) {
+ if (validLayoutRequesters == null) {
+ validLayoutRequesters = new ArrayList<View>();
+ }
+ validLayoutRequesters.add(view);
+ }
+ }
+ }
+ if (!secondLayoutRequests) {
+ // If we're checking the layout flags, then we need to clean them up also
+ for (int i = 0; i < numViewsRequestingLayout; ++i) {
+ View view = layoutRequesters.get(i);
+ while (view != null &&
+ (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
+ view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
+ if (view.mParent instanceof View) {
+ view = (View) view.mParent;
+ } else {
+ view = null;
+ }
+ }
+ }
+ }
+ layoutRequesters.clear();
+ return validLayoutRequesters;
}
public void requestTransparentRegion(View child) {
@@ -1985,31 +2175,25 @@ public final class ViewRootImpl implements ViewParent,
private void profileRendering(boolean enabled) {
if (mProfileRendering) {
mRenderProfilingEnabled = enabled;
- if (mRenderProfiler == null) {
- mRenderProfiler = new Thread(new Runnable() {
- @Override
- public void run() {
- Log.d(TAG, "Starting profiling thread");
- while (mRenderProfilingEnabled) {
- mAttachInfo.mHandler.post(new Runnable() {
- @Override
- public void run() {
- mDirty.set(0, 0, mWidth, mHeight);
- scheduleTraversals();
- }
- });
- try {
- // TODO: This should use vsync when we get an API
- Thread.sleep(15);
- } catch (InterruptedException e) {
- Log.d(TAG, "Exiting profiling thread");
- }
+
+ if (mRenderProfiler != null) {
+ mChoreographer.removeFrameCallback(mRenderProfiler);
+ }
+ if (mRenderProfilingEnabled) {
+ if (mRenderProfiler == null) {
+ mRenderProfiler = new Choreographer.FrameCallback() {
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ mDirty.set(0, 0, mWidth, mHeight);
+ scheduleTraversals();
+ if (mRenderProfilingEnabled) {
+ mChoreographer.postFrameCallback(mRenderProfiler);
+ }
}
- }
- }, "Rendering Profiler");
- mRenderProfiler.start();
+ };
+ }
+ mChoreographer.postFrameCallback(mRenderProfiler);
} else {
- mRenderProfiler.interrupt();
mRenderProfiler = null;
}
}
@@ -2085,7 +2269,7 @@ public final class ViewRootImpl implements ViewParent,
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
- if (surface == null || !surface.isValid()) {
+ if (!surface.isValid()) {
return;
}
@@ -2166,6 +2350,8 @@ public final class ViewRootImpl implements ViewParent,
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
+ invalidateDisplayLists();
+
attachInfo.mTreeObserver.dispatchOnDraw();
if (!dirty.isEmpty() || mIsAnimating) {
@@ -2176,16 +2362,39 @@ public final class ViewRootImpl implements ViewParent,
mResizeAlpha = resizeAlpha;
mCurrentDirty.set(dirty);
- mCurrentDirty.union(mPreviousDirty);
- mPreviousDirty.set(dirty);
dirty.setEmpty();
- if (attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
- animating ? null : mCurrentDirty)) {
- mPreviousDirty.set(0, 0, mWidth, mHeight);
+ attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
+ animating ? null : mCurrentDirty);
+ } else {
+ // If we get here with a disabled & requested hardware renderer, something went
+ // wrong (an invalidate posted right before we destroyed the hardware surface
+ // for instance) so we should just bail out. Locking the surface with software
+ // rendering at this point would lock it forever and prevent hardware renderer
+ // from doing its job when it comes back.
+ // Before we request a new frame we must however attempt to reinitiliaze the
+ // hardware renderer if it's in requested state. This would happen after an
+ // eglTerminate() for instance.
+ if (attachInfo.mHardwareRenderer != null &&
+ !attachInfo.mHardwareRenderer.isEnabled() &&
+ attachInfo.mHardwareRenderer.isRequested()) {
+
+ try {
+ attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
+ mHolder.getSurface());
+ } catch (Surface.OutOfResourcesException e) {
+ handleOutOfResourcesException(e);
+ return;
+ }
+
+ mFullRedrawNeeded = true;
+ scheduleTraversals();
+ return;
+ }
+
+ if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
+ return;
}
- } else if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
- return;
}
}
@@ -2201,18 +2410,6 @@ public final class ViewRootImpl implements ViewParent,
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
boolean scalingRequired, Rect dirty) {
- // If we get here with a disabled & requested hardware renderer, something went
- // wrong (an invalidate posted right before we destroyed the hardware surface
- // for instance) so we should just bail out. Locking the surface with software
- // rendering at this point would lock it forever and prevent hardware renderer
- // from doing its job when it comes back.
- if (attachInfo.mHardwareRenderer != null && !attachInfo.mHardwareRenderer.isEnabled() &&
- attachInfo.mHardwareRenderer.isRequested()) {
- mFullRedrawNeeded = true;
- scheduleTraversals();
- return false;
- }
-
// Draw with software renderer.
Canvas canvas;
try {
@@ -2223,6 +2420,8 @@ public final class ViewRootImpl implements ViewParent,
canvas = mSurface.lockCanvas(dirty);
+ // The dirty rectangle can be modified by Surface.lockCanvas()
+ //noinspection ConstantConditions
if (left != dirty.left || top != dirty.top || right != dirty.right ||
bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
@@ -2231,15 +2430,7 @@ public final class ViewRootImpl implements ViewParent,
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
- Log.e(TAG, "OutOfResourcesException locking surface", e);
- try {
- if (!mWindowSession.outOfMemory(mWindow)) {
- Slog.w(TAG, "No processes killed for memory; killing self");
- Process.killProcess(Process.myPid());
- }
- } catch (RemoteException ex) {
- }
- mLayoutRequested = true; // ask wm for a new surface next time.
+ handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not lock surface", e);
@@ -2376,13 +2567,24 @@ public final class ViewRootImpl implements ViewParent,
for (int i = 0; i < count; i++) {
final DisplayList displayList = displayLists.get(i);
- displayList.invalidate();
- displayList.clear();
+ if (displayList.isDirty()) {
+ displayList.clear();
+ }
}
displayLists.clear();
}
+ /**
+ * @hide
+ */
+ public void setDrawDuringWindowsAnimating(boolean value) {
+ mDrawDuringWindowsAnimating = value;
+ if (value) {
+ handleDispatchDoneAnimating();
+ }
+ }
+
boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
final View.AttachInfo attachInfo = mAttachInfo;
final Rect ci = attachInfo.mContentInsets;
@@ -2403,17 +2605,12 @@ public final class ViewRootImpl implements ViewParent,
// requestChildRectangleOnScreen() call (in which case 'rectangle'
// is non-null and we just want to scroll to whatever that
// rectangle is).
- View focus = mRealFocusedView;
-
- // When in touch mode, focus points to the previously focused view,
- // which may have been removed from the view hierarchy. The following
- // line checks whether the view is still in our hierarchy.
- if (focus == null || focus.mAttachInfo != mAttachInfo) {
- mRealFocusedView = null;
+ final View focus = mView.findFocus();
+ if (focus == null) {
return false;
}
-
- if (focus != mLastScrolledFocus) {
+ View lastScrolledFocus = (mLastScrolledFocus != null) ? mLastScrolledFocus.get() : null;
+ if (focus != lastScrolledFocus) {
// If the focus has changed, then ignore any requests to scroll
// to a rectangle; first we want to make sure the entire focus
// view is visible.
@@ -2422,18 +2619,17 @@ public final class ViewRootImpl implements ViewParent,
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Eval scroll: focus=" + focus
+ " rectangle=" + rectangle + " ci=" + ci
+ " vi=" + vi);
- if (focus == mLastScrolledFocus && !mScrollMayChange
- && rectangle == null) {
+ if (focus == lastScrolledFocus && !mScrollMayChange && rectangle == null) {
// Optimization: if the focus hasn't changed since last
// time, and no layout has happened, then just leave things
// as they are.
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Keeping scroll y="
+ mScrollY + " vi=" + vi.toShortString());
- } else if (focus != null) {
+ } else {
// We need to determine if the currently focused view is
// within the visible part of the window and, if not, apply
// a pan so it can be seen.
- mLastScrolledFocus = focus;
+ mLastScrolledFocus = new WeakReference<View>(focus);
mScrollMayChange = false;
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Need to scroll?");
// Try to find the rectangle from the focus view.
@@ -2526,7 +2722,6 @@ public final class ViewRootImpl implements ViewParent,
AccessibilityNodeInfo focusNode = mAccessibilityFocusedVirtualView;
View focusHost = mAccessibilityFocusedHost;
- focusHost.clearAccessibilityFocusNoCallbacks();
// Wipe the state of the current accessibility focus since
// the call into the provider to clear accessibility focus
@@ -2536,6 +2731,10 @@ public final class ViewRootImpl implements ViewParent,
mAccessibilityFocusedHost = null;
mAccessibilityFocusedVirtualView = null;
+ // Clear accessibility focus on the host after clearing state since
+ // this method may be reentrant.
+ focusHost.clearAccessibilityFocusNoCallbacks();
+
AccessibilityNodeProvider provider = focusHost.getAccessibilityNodeProvider();
if (provider != null) {
// Invalidate the area of the cleared accessibility focus.
@@ -2560,33 +2759,19 @@ public final class ViewRootImpl implements ViewParent,
}
public void requestChildFocus(View child, View focused) {
- checkThread();
-
if (DEBUG_INPUT_RESIZE) {
Log.v(TAG, "Request child focus: focus now " + focused);
}
-
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, focused);
+ checkThread();
scheduleTraversals();
-
- mFocusedView = mRealFocusedView = focused;
}
public void clearChildFocus(View child) {
- checkThread();
-
if (DEBUG_INPUT_RESIZE) {
Log.v(TAG, "Clearing child focus");
}
-
- mOldFocusedView = mFocusedView;
-
- // Invoke the listener only if there is no view to take focus
- if (focusSearch(null, View.FOCUS_FORWARD) == null) {
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, null);
- }
-
- mFocusedView = mRealFocusedView = null;
+ checkThread();
+ scheduleTraversals();
}
@Override
@@ -2603,14 +2788,13 @@ public final class ViewRootImpl implements ViewParent,
// the one case where will transfer focus away from the current one
// is if the current view is a view group that prefers to give focus
// to its children first AND the view is a descendant of it.
- mFocusedView = mView.findFocus();
- boolean descendantsHaveDibsOnFocus =
- (mFocusedView instanceof ViewGroup) &&
- (((ViewGroup) mFocusedView).getDescendantFocusability() ==
- ViewGroup.FOCUS_AFTER_DESCENDANTS);
- if (descendantsHaveDibsOnFocus && isViewDescendantOf(v, mFocusedView)) {
- // If a view gets the focus, the listener will be invoked from requestChildFocus()
- v.requestFocus();
+ View focused = mView.findFocus();
+ if (focused instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) focused;
+ if (group.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS
+ && isViewDescendantOf(v, focused)) {
+ v.requestFocus();
+ }
}
}
}
@@ -2632,6 +2816,7 @@ public final class ViewRootImpl implements ViewParent,
mAttachInfo.mHardwareRenderer.isEnabled()) {
mAttachInfo.mHardwareRenderer.validate();
}
+ mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow();
}
@@ -2644,6 +2829,7 @@ public final class ViewRootImpl implements ViewParent,
setAccessibilityFocus(null, null);
+ mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
mAttachInfo.mSurface = null;
@@ -2652,9 +2838,11 @@ public final class ViewRootImpl implements ViewParent,
if (mInputQueueCallback != null && mInputQueue != null) {
mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
+ mInputQueue.dispose();
mInputQueueCallback = null;
mInputQueue = null;
- } else if (mInputEventReceiver != null) {
+ }
+ if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
mInputEventReceiver = null;
}
@@ -2662,7 +2850,7 @@ public final class ViewRootImpl implements ViewParent,
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
-
+
// Dispose the input channel after removing the window so the Window Manager
// doesn't interpret the input channel being closed as an abnormal termination.
if (mInputChannel != null) {
@@ -2740,7 +2928,6 @@ public final class ViewRootImpl implements ViewParent,
private final static int MSG_DISPATCH_KEY = 7;
private final static int MSG_DISPATCH_APP_VISIBILITY = 8;
private final static int MSG_DISPATCH_GET_NEW_SURFACE = 9;
- private final static int MSG_IME_FINISHED_EVENT = 10;
private final static int MSG_DISPATCH_KEY_FROM_IME = 11;
private final static int MSG_FINISH_INPUT_CONNECTION = 12;
private final static int MSG_CHECK_FOCUS = 13;
@@ -2751,11 +2938,10 @@ public final class ViewRootImpl implements ViewParent,
private final static int MSG_UPDATE_CONFIGURATION = 18;
private final static int MSG_PROCESS_INPUT_EVENTS = 19;
private final static int MSG_DISPATCH_SCREEN_STATE = 20;
- private final static int MSG_INVALIDATE_DISPLAY_LIST = 21;
- 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;
+ private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 21;
+ private final static int MSG_DISPATCH_DONE_ANIMATING = 22;
+ private final static int MSG_INVALIDATE_WORLD = 23;
+ private final static int MSG_WINDOW_MOVED = 24;
final class ViewRootHandler extends Handler {
@Override
@@ -2779,8 +2965,6 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_DISPATCH_APP_VISIBILITY";
case MSG_DISPATCH_GET_NEW_SURFACE:
return "MSG_DISPATCH_GET_NEW_SURFACE";
- case MSG_IME_FINISHED_EVENT:
- return "MSG_IME_FINISHED_EVENT";
case MSG_DISPATCH_KEY_FROM_IME:
return "MSG_DISPATCH_KEY_FROM_IME";
case MSG_FINISH_INPUT_CONNECTION:
@@ -2801,8 +2985,6 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_PROCESS_INPUT_EVENTS";
case MSG_DISPATCH_SCREEN_STATE:
return "MSG_DISPATCH_SCREEN_STATE";
- case MSG_INVALIDATE_DISPLAY_LIST:
- return "MSG_INVALIDATE_DISPLAY_LIST";
case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST:
return "MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST";
case MSG_DISPATCH_DONE_ANIMATING:
@@ -2822,10 +3004,7 @@ public final class ViewRootImpl implements ViewParent,
case MSG_INVALIDATE_RECT:
final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
info.target.invalidate(info.left, info.top, info.right, info.bottom);
- info.release();
- break;
- case MSG_IME_FINISHED_EVENT:
- handleImeFinishedEvent(msg.arg1, msg.arg2 != 0);
+ info.recycle();
break;
case MSG_PROCESS_INPUT_EVENTS:
mProcessInputEventsScheduled = false;
@@ -2841,6 +3020,7 @@ public final class ViewRootImpl implements ViewParent,
// Recycled in the fall through...
SomeArgs args = (SomeArgs) msg.obj;
if (mWinFrame.equals(args.arg1)
+ && mPendingOverscanInsets.equals(args.arg5)
&& mPendingContentInsets.equals(args.arg2)
&& mPendingVisibleInsets.equals(args.arg3)
&& args.arg4 == null) {
@@ -2857,6 +3037,7 @@ public final class ViewRootImpl implements ViewParent,
}
mWinFrame.set((Rect) args.arg1);
+ mPendingOverscanInsets.set((Rect) args.arg5);
mPendingContentInsets.set((Rect) args.arg2);
mPendingVisibleInsets.set((Rect) args.arg3);
@@ -2901,14 +3082,11 @@ public final class ViewRootImpl implements ViewParent,
boolean inTouchMode = msg.arg2 != 0;
ensureTouchModeLocally(inTouchMode);
- if (mAttachInfo.mHardwareRenderer != null &&
- mSurface != null && mSurface.isValid()) {
+ if (mAttachInfo.mHardwareRenderer != null && mSurface.isValid()){
mFullRedrawNeeded = true;
try {
- if (mAttachInfo.mHardwareRenderer.initializeIfNeeded(
- mWidth, mHeight, mHolder.getSurface())) {
- mFullRedrawNeeded = true;
- }
+ mAttachInfo.mHardwareRenderer.initializeIfNeeded(
+ mWidth, mHeight, mHolder.getSurface());
} catch (Surface.OutOfResourcesException e) {
Log.e(TAG, "OutOfResourcesException locking surface", e);
try {
@@ -2935,6 +3113,7 @@ public final class ViewRootImpl implements ViewParent,
}
mAttachInfo.mKeyDispatchState.reset();
mView.dispatchWindowFocusChanged(hasWindowFocus);
+ mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
}
// Note: must be done after the focus change callbacks,
@@ -3009,7 +3188,7 @@ public final class ViewRootImpl implements ViewParent,
handleDragEvent(event);
} break;
case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
- handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo)msg.obj);
+ handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
} break;
case MSG_UPDATE_CONFIGURATION: {
Configuration config = (Configuration)msg.obj;
@@ -3023,9 +3202,6 @@ public final class ViewRootImpl implements ViewParent,
handleScreenStateChange(msg.arg1 == 1);
}
} break;
- case MSG_INVALIDATE_DISPLAY_LIST: {
- invalidateDisplayLists();
- } break;
case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
setAccessibilityFocus(null, null);
} break;
@@ -3094,7 +3270,6 @@ public final class ViewRootImpl implements ViewParent,
// set yet.
final View focused = mView.findFocus();
if (focused != null && !focused.isFocusableInTouchMode()) {
-
final ViewGroup ancestorToTakeFocus =
findAncestorToTakeFocusInTouchMode(focused);
if (ancestorToTakeFocus != null) {
@@ -3103,10 +3278,7 @@ public final class ViewRootImpl implements ViewParent,
return ancestorToTakeFocus.requestFocus();
} else {
// nothing appropriate to have focus in touch mode, clear it out
- mView.unFocus();
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
- mFocusedView = null;
- mOldFocusedView = null;
+ focused.unFocus();
return true;
}
}
@@ -3141,12 +3313,11 @@ public final class ViewRootImpl implements ViewParent,
private boolean leaveTouchMode() {
if (mView != null) {
if (mView.hasFocus()) {
- // i learned the hard way to not trust mFocusedView :)
- mFocusedView = mView.findFocus();
- if (!(mFocusedView instanceof ViewGroup)) {
+ View focusedView = mView.findFocus();
+ if (!(focusedView instanceof ViewGroup)) {
// some view has focus, let it keep it
return false;
- } else if (((ViewGroup)mFocusedView).getDescendantFocusability() !=
+ } else if (((ViewGroup) focusedView).getDescendantFocusability() !=
ViewGroup.FOCUS_AFTER_DESCENDANTS) {
// some view group has focus, and doesn't prefer its children
// over itself for focus, so let them keep it.
@@ -3164,385 +3335,1454 @@ public final class ViewRootImpl implements ViewParent,
return false;
}
- private int deliverInputEvent(QueuedInputEvent q) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent");
- try {
- if (q.mEvent instanceof KeyEvent) {
- return deliverKeyEvent(q);
+ /**
+ * Base class for implementing a stage in the chain of responsibility
+ * for processing input events.
+ * <p>
+ * Events are delivered to the stage by the {@link #deliver} method. The stage
+ * then has the choice of finishing the event or forwarding it to the next stage.
+ * </p>
+ */
+ abstract class InputStage {
+ private final InputStage mNext;
+
+ protected static final int FORWARD = 0;
+ protected static final int FINISH_HANDLED = 1;
+ protected static final int FINISH_NOT_HANDLED = 2;
+
+ /**
+ * Creates an input stage.
+ * @param next The next stage to which events should be forwarded.
+ */
+ public InputStage(InputStage next) {
+ mNext = next;
+ }
+
+ /**
+ * Delivers an event to be processed.
+ */
+ public final void deliver(QueuedInputEvent q) {
+ if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
+ forward(q);
+ } else if (mView == null || !mAdded) {
+ Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent);
+ finish(q, false);
+ } else if (!mAttachInfo.mHasWindowFocus &&
+ !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) &&
+ !isTerminalInputEvent(q.mEvent)) {
+ // If this is a focused event and the window doesn't currently have input focus,
+ // then drop this event. This could be an event that came back from the previous
+ // stage but the window has lost focus in the meantime.
+ Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent);
+ finish(q, false);
} else {
- final int source = q.mEvent.getSource();
- if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
- return deliverPointerEvent(q);
- } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
- return deliverTrackballEvent(q);
- } else {
- return deliverGenericMotionEvent(q);
- }
+ apply(q, onProcess(q));
}
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
- }
- private int deliverInputEventPostIme(QueuedInputEvent q) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEventPostIme");
- try {
- if (q.mEvent instanceof KeyEvent) {
- return deliverKeyEventPostIme(q);
+ /**
+ * Marks the the input event as finished then forwards it to the next stage.
+ */
+ protected void finish(QueuedInputEvent q, boolean handled) {
+ q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
+ if (handled) {
+ q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;
+ }
+ forward(q);
+ }
+
+ /**
+ * Forwards the event to the next stage.
+ */
+ protected void forward(QueuedInputEvent q) {
+ onDeliverToNext(q);
+ }
+
+ /**
+ * Applies a result code from {@link #onProcess} to the specified event.
+ */
+ protected void apply(QueuedInputEvent q, int result) {
+ if (result == FORWARD) {
+ forward(q);
+ } else if (result == FINISH_HANDLED) {
+ finish(q, true);
+ } else if (result == FINISH_NOT_HANDLED) {
+ finish(q, false);
} else {
- final int source = q.mEvent.getSource();
- if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
- return deliverTrackballEventPostIme(q);
+ throw new IllegalArgumentException("Invalid result: " + result);
+ }
+ }
+
+ /**
+ * Called when an event is ready to be processed.
+ * @return A result code indicating how the event was handled.
+ */
+ protected int onProcess(QueuedInputEvent q) {
+ return FORWARD;
+ }
+
+ /**
+ * Called when an event is being delivered to the next stage.
+ */
+ protected void onDeliverToNext(QueuedInputEvent q) {
+ if (mNext != null) {
+ mNext.deliver(q);
+ } else {
+ finishInputEvent(q);
+ }
+ }
+ }
+
+ /**
+ * Base class for implementing an input pipeline stage that supports
+ * asynchronous and out-of-order processing of input events.
+ * <p>
+ * In addition to what a normal input stage can do, an asynchronous
+ * input stage may also defer an input event that has been delivered to it
+ * and finish or forward it later.
+ * </p>
+ */
+ abstract class AsyncInputStage extends InputStage {
+ private final String mTraceCounter;
+
+ private QueuedInputEvent mQueueHead;
+ private QueuedInputEvent mQueueTail;
+ private int mQueueLength;
+
+ protected static final int DEFER = 3;
+
+ /**
+ * Creates an asynchronous input stage.
+ * @param next The next stage to which events should be forwarded.
+ * @param traceCounter The name of a counter to record the size of
+ * the queue of pending events.
+ */
+ public AsyncInputStage(InputStage next, String traceCounter) {
+ super(next);
+ mTraceCounter = traceCounter;
+ }
+
+ /**
+ * Marks the event as deferred, which is to say that it will be handled
+ * asynchronously. The caller is responsible for calling {@link #forward}
+ * or {@link #finish} later when it is done handling the event.
+ */
+ protected void defer(QueuedInputEvent q) {
+ q.mFlags |= QueuedInputEvent.FLAG_DEFERRED;
+ enqueue(q);
+ }
+
+ @Override
+ protected void forward(QueuedInputEvent q) {
+ // Clear the deferred flag.
+ q.mFlags &= ~QueuedInputEvent.FLAG_DEFERRED;
+
+ // Fast path if the queue is empty.
+ QueuedInputEvent curr = mQueueHead;
+ if (curr == null) {
+ super.forward(q);
+ return;
+ }
+
+ // Determine whether the event must be serialized behind any others
+ // before it can be delivered to the next stage. This is done because
+ // deferred events might be handled out of order by the stage.
+ final int deviceId = q.mEvent.getDeviceId();
+ QueuedInputEvent prev = null;
+ boolean blocked = false;
+ while (curr != null && curr != q) {
+ if (!blocked && deviceId == curr.mEvent.getDeviceId()) {
+ blocked = true;
+ }
+ prev = curr;
+ curr = curr.mNext;
+ }
+
+ // If the event is blocked, then leave it in the queue to be delivered later.
+ // Note that the event might not yet be in the queue if it was not previously
+ // deferred so we will enqueue it if needed.
+ if (blocked) {
+ if (curr == null) {
+ enqueue(q);
+ }
+ return;
+ }
+
+ // The event is not blocked. Deliver it immediately.
+ if (curr != null) {
+ curr = curr.mNext;
+ dequeue(q, prev);
+ }
+ super.forward(q);
+
+ // Dequeuing this event may have unblocked successors. Deliver them.
+ while (curr != null) {
+ if (deviceId == curr.mEvent.getDeviceId()) {
+ if ((curr.mFlags & QueuedInputEvent.FLAG_DEFERRED) != 0) {
+ break;
+ }
+ QueuedInputEvent next = curr.mNext;
+ dequeue(curr, prev);
+ super.forward(curr);
+ curr = next;
} else {
- return deliverGenericMotionEventPostIme(q);
+ prev = curr;
+ curr = curr.mNext;
}
}
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
- }
- private int deliverPointerEvent(QueuedInputEvent q) {
- final MotionEvent event = (MotionEvent)q.mEvent;
- final boolean isTouchEvent = event.isTouchEvent();
- if (mInputEventConsistencyVerifier != null) {
- if (isTouchEvent) {
- mInputEventConsistencyVerifier.onTouchEvent(event, 0);
+ @Override
+ protected void apply(QueuedInputEvent q, int result) {
+ if (result == DEFER) {
+ defer(q);
+ } else {
+ super.apply(q, result);
+ }
+ }
+
+ private void enqueue(QueuedInputEvent q) {
+ if (mQueueTail == null) {
+ mQueueHead = q;
+ mQueueTail = q;
} else {
- mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0);
+ mQueueTail.mNext = q;
+ mQueueTail = q;
}
+
+ mQueueLength += 1;
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength);
}
- // If there is no view, then the event will not be handled.
- if (mView == null || !mAdded) {
- return EVENT_NOT_HANDLED;
+ private void dequeue(QueuedInputEvent q, QueuedInputEvent prev) {
+ if (prev == null) {
+ mQueueHead = q.mNext;
+ } else {
+ prev.mNext = q.mNext;
+ }
+ if (mQueueTail == q) {
+ mQueueTail = prev;
+ }
+ q.mNext = null;
+
+ mQueueLength -= 1;
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength);
}
+ }
- // Translate the pointer event for compatibility, if needed.
- if (mTranslator != null) {
- mTranslator.translateEventInScreenToAppWindow(event);
+ /**
+ * Delivers pre-ime input events to a native activity.
+ * Does not support pointer events.
+ */
+ final class NativePreImeInputStage extends AsyncInputStage
+ implements InputQueue.FinishedInputEventCallback {
+ public NativePreImeInputStage(InputStage next, String traceCounter) {
+ super(next, traceCounter);
}
- // Enter touch mode on down or scroll.
- final int action = event.getAction();
- if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) {
- ensureTouchMode(true);
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ if (mInputQueue != null && q.mEvent instanceof KeyEvent) {
+ mInputQueue.sendInputEvent(q.mEvent, q, true, this);
+ return DEFER;
+ }
+ return FORWARD;
}
- // Offset the scroll position.
- if (mCurScrollY != 0) {
- event.offsetLocation(0, mCurScrollY);
+ @Override
+ public void onFinishedInputEvent(Object token, boolean handled) {
+ QueuedInputEvent q = (QueuedInputEvent)token;
+ if (handled) {
+ finish(q, true);
+ return;
+ }
+ forward(q);
}
- if (MEASURE_LATENCY) {
- lt.sample("A Dispatching PointerEvents", System.nanoTime() - event.getEventTimeNano());
+ }
+
+ /**
+ * Delivers pre-ime input events to the view hierarchy.
+ * Does not support pointer events.
+ */
+ final class ViewPreImeInputStage extends InputStage {
+ public ViewPreImeInputStage(InputStage next) {
+ super(next);
}
- // Remember the touch position for possible drag-initiation.
- if (isTouchEvent) {
- mLastTouchPoint.x = event.getRawX();
- mLastTouchPoint.y = event.getRawY();
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ if (q.mEvent instanceof KeyEvent) {
+ return processKeyEvent(q);
+ }
+ return FORWARD;
}
- // Dispatch touch to view hierarchy.
- boolean handled = mView.dispatchPointerEvent(event);
- if (MEASURE_LATENCY) {
- lt.sample("B Dispatched PointerEvents ", System.nanoTime() - event.getEventTimeNano());
+ private int processKeyEvent(QueuedInputEvent q) {
+ final KeyEvent event = (KeyEvent)q.mEvent;
+ if (mView.dispatchKeyEventPreIme(event)) {
+ return FINISH_HANDLED;
+ }
+ return FORWARD;
}
- return handled ? EVENT_HANDLED : EVENT_NOT_HANDLED;
}
- private int deliverTrackballEvent(QueuedInputEvent q) {
- final MotionEvent event = (MotionEvent)q.mEvent;
- if (mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
+ /**
+ * Delivers input events to the ime.
+ * Does not support pointer events.
+ */
+ final class ImeInputStage extends AsyncInputStage
+ implements InputMethodManager.FinishedInputEventCallback {
+ public ImeInputStage(InputStage next, String traceCounter) {
+ super(next, traceCounter);
}
- 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.
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
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);
- int result = imm.dispatchTrackballEvent(mView.getContext(), seq, event,
- mInputMethodCallback);
- if (result != EVENT_NOT_HANDLED) {
- return result;
+ final InputEvent event = q.mEvent;
+ if (DEBUG_IMF) Log.v(TAG, "Sending input event to IME: " + event);
+ int result = imm.dispatchInputEvent(event, q, this, mHandler);
+ if (result == InputMethodManager.DISPATCH_HANDLED) {
+ return FINISH_HANDLED;
+ } else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {
+ return FINISH_NOT_HANDLED;
+ } else {
+ return DEFER; // callback will be invoked later
}
}
}
+ return FORWARD;
}
- // Not dispatching to IME, continue with post IME actions.
- return deliverTrackballEventPostIme(q);
+ @Override
+ public void onFinishedInputEvent(Object token, boolean handled) {
+ QueuedInputEvent q = (QueuedInputEvent)token;
+ if (handled) {
+ finish(q, true);
+ return;
+ }
+ forward(q);
+ }
}
- private int deliverTrackballEventPostIme(QueuedInputEvent q) {
- final MotionEvent event = (MotionEvent) q.mEvent;
+ /**
+ * Performs early processing of post-ime input events.
+ */
+ final class EarlyPostImeInputStage extends InputStage {
+ public EarlyPostImeInputStage(InputStage next) {
+ super(next);
+ }
- // If there is no view, then the event will not be handled.
- if (mView == null || !mAdded) {
- return EVENT_NOT_HANDLED;
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ if (q.mEvent instanceof KeyEvent) {
+ return processKeyEvent(q);
+ } else {
+ final int source = q.mEvent.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ return processPointerEvent(q);
+ }
+ }
+ return FORWARD;
}
- // Deliver the trackball event to the view.
- if (mView.dispatchTrackballEvent(event)) {
- // If we reach this, we delivered a trackball event to mView and
- // mView consumed it. Because we will not translate the trackball
- // event into a key event, touch mode will not exit, so we exit
- // touch mode here.
- ensureTouchMode(false);
- mLastTrackballTime = Integer.MIN_VALUE;
- return EVENT_HANDLED;
+ private int processKeyEvent(QueuedInputEvent q) {
+ final KeyEvent event = (KeyEvent)q.mEvent;
+
+ // If the key's purpose is to exit touch mode then we consume it
+ // and consider it handled.
+ if (checkForLeavingTouchModeAndConsume(event)) {
+ return FINISH_HANDLED;
+ }
+
+ // Make sure the fallback event policy sees all keys that will be
+ // delivered to the view hierarchy.
+ mFallbackEventHandler.preDispatchKeyEvent(event);
+ return FORWARD;
}
- // Translate the trackball event into DPAD keys and try to deliver those.
- final TrackballAxis x = mTrackballAxisX;
- final TrackballAxis y = mTrackballAxisY;
+ private int processPointerEvent(QueuedInputEvent q) {
+ final MotionEvent event = (MotionEvent)q.mEvent;
+
+ // Translate the pointer event for compatibility, if needed.
+ if (mTranslator != null) {
+ mTranslator.translateEventInScreenToAppWindow(event);
+ }
+
+ // Enter touch mode on down or scroll.
+ final int action = event.getAction();
+ if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) {
+ ensureTouchMode(true);
+ }
- long curTime = SystemClock.uptimeMillis();
- if ((mLastTrackballTime + MAX_TRACKBALL_DELAY) < curTime) {
- // It has been too long since the last movement,
- // so restart at the beginning.
- x.reset(0);
- y.reset(0);
- mLastTrackballTime = curTime;
+ // Offset the scroll position.
+ if (mCurScrollY != 0) {
+ event.offsetLocation(0, mCurScrollY);
+ }
+
+ // Remember the touch position for possible drag-initiation.
+ if (event.isTouchEvent()) {
+ mLastTouchPoint.x = event.getRawX();
+ mLastTouchPoint.y = event.getRawY();
+ }
+ return FORWARD;
}
+ }
- final int action = event.getAction();
- final int metaState = event.getMetaState();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- x.reset(2);
- y.reset(2);
- enqueueInputEvent(new KeyEvent(curTime, curTime,
- KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
- InputDevice.SOURCE_KEYBOARD));
- break;
- case MotionEvent.ACTION_UP:
- x.reset(2);
- y.reset(2);
- enqueueInputEvent(new KeyEvent(curTime, curTime,
- KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
- InputDevice.SOURCE_KEYBOARD));
- break;
+ /**
+ * Delivers post-ime input events to a native activity.
+ */
+ final class NativePostImeInputStage extends AsyncInputStage
+ implements InputQueue.FinishedInputEventCallback {
+ public NativePostImeInputStage(InputStage next, String traceCounter) {
+ super(next, traceCounter);
}
- if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + x.position + " step="
- + x.step + " dir=" + x.dir + " acc=" + x.acceleration
- + " move=" + event.getX()
- + " / Y=" + y.position + " step="
- + y.step + " dir=" + y.dir + " acc=" + y.acceleration
- + " move=" + event.getY());
- final float xOff = x.collect(event.getX(), event.getEventTime(), "X");
- final float yOff = y.collect(event.getY(), event.getEventTime(), "Y");
-
- // Generate DPAD events based on the trackball movement.
- // We pick the axis that has moved the most as the direction of
- // the DPAD. When we generate DPAD events for one axis, then the
- // other axis is reset -- we don't want to perform DPAD jumps due
- // to slight movements in the trackball when making major movements
- // along the other axis.
- int keycode = 0;
- int movement = 0;
- float accel = 1;
- if (xOff > yOff) {
- movement = x.generate((2/event.getXPrecision()));
- if (movement != 0) {
- keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT
- : KeyEvent.KEYCODE_DPAD_LEFT;
- accel = x.acceleration;
- y.reset(2);
- }
- } else if (yOff > 0) {
- movement = y.generate((2/event.getYPrecision()));
- if (movement != 0) {
- keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN
- : KeyEvent.KEYCODE_DPAD_UP;
- accel = y.acceleration;
- x.reset(2);
- }
- }
-
- if (keycode != 0) {
- if (movement < 0) movement = -movement;
- int accelMovement = (int)(movement * accel);
- if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement
- + " accelMovement=" + accelMovement
- + " accel=" + accel);
- if (accelMovement > movement) {
- if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: "
- + keycode);
- movement--;
- int repeatCount = accelMovement - movement;
- enqueueInputEvent(new KeyEvent(curTime, curTime,
- KeyEvent.ACTION_MULTIPLE, keycode, repeatCount, metaState,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
- InputDevice.SOURCE_KEYBOARD));
- }
- while (movement > 0) {
- if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: "
- + keycode);
- movement--;
- curTime = SystemClock.uptimeMillis();
- enqueueInputEvent(new KeyEvent(curTime, curTime,
- KeyEvent.ACTION_DOWN, keycode, 0, metaState,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
- InputDevice.SOURCE_KEYBOARD));
- enqueueInputEvent(new KeyEvent(curTime, curTime,
- KeyEvent.ACTION_UP, keycode, 0, metaState,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
- InputDevice.SOURCE_KEYBOARD));
- }
- mLastTrackballTime = curTime;
- }
-
- // Unfortunately we can't tell whether the application consumed the keys, so
- // we always consider the trackball event handled.
- return EVENT_HANDLED;
- }
-
- private int deliverGenericMotionEvent(QueuedInputEvent q) {
- final MotionEvent event = (MotionEvent)q.mEvent;
- if (mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onGenericMotionEvent(event, 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);
- int result = imm.dispatchGenericMotionEvent(mView.getContext(), seq, event,
- mInputMethodCallback);
- if (result != EVENT_NOT_HANDLED) {
- return result;
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ if (mInputQueue != null) {
+ mInputQueue.sendInputEvent(q.mEvent, q, false, this);
+ return DEFER;
+ }
+ return FORWARD;
+ }
+
+ @Override
+ public void onFinishedInputEvent(Object token, boolean handled) {
+ QueuedInputEvent q = (QueuedInputEvent)token;
+ if (handled) {
+ finish(q, true);
+ return;
+ }
+ forward(q);
+ }
+ }
+
+ /**
+ * Delivers post-ime input events to the view hierarchy.
+ */
+ final class ViewPostImeInputStage extends InputStage {
+ public ViewPostImeInputStage(InputStage next) {
+ super(next);
+ }
+
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ if (q.mEvent instanceof KeyEvent) {
+ return processKeyEvent(q);
+ } else {
+ final int source = q.mEvent.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ return processPointerEvent(q);
+ } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ return processTrackballEvent(q);
+ } else {
+ return processGenericMotionEvent(q);
+ }
+ }
+ }
+
+ private int processKeyEvent(QueuedInputEvent q) {
+ final KeyEvent event = (KeyEvent)q.mEvent;
+
+ // Deliver the key to the view hierarchy.
+ if (mView.dispatchKeyEvent(event)) {
+ return FINISH_HANDLED;
+ }
+
+ // If the Control modifier is held, try to interpret the key as a shortcut.
+ if (event.getAction() == KeyEvent.ACTION_DOWN
+ && event.isCtrlPressed()
+ && event.getRepeatCount() == 0
+ && !KeyEvent.isModifierKey(event.getKeyCode())) {
+ if (mView.dispatchKeyShortcutEvent(event)) {
+ return FINISH_HANDLED;
+ }
+ }
+
+ // Apply the fallback event policy.
+ if (mFallbackEventHandler.dispatchKeyEvent(event)) {
+ return FINISH_HANDLED;
+ }
+
+ // Handle automatic focus changes.
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ int direction = 0;
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_LEFT;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_RIGHT;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_UP;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_DOWN;
+ }
+ break;
+ case KeyEvent.KEYCODE_TAB:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_FORWARD;
+ } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
+ direction = View.FOCUS_BACKWARD;
+ }
+ break;
+ }
+ if (direction != 0) {
+ View focused = mView.findFocus();
+ if (focused != null) {
+ View v = focused.focusSearch(direction);
+ if (v != null && v != focused) {
+ // do the math the get the interesting rect
+ // of previous focused into the coord system of
+ // newly focused view
+ focused.getFocusedRect(mTempRect);
+ if (mView instanceof ViewGroup) {
+ ((ViewGroup) mView).offsetDescendantRectToMyCoords(
+ focused, mTempRect);
+ ((ViewGroup) mView).offsetRectIntoDescendantCoords(
+ v, mTempRect);
+ }
+ if (v.requestFocus(direction, mTempRect)) {
+ playSoundEffect(SoundEffectConstants
+ .getContantForFocusDirection(direction));
+ return FINISH_HANDLED;
+ }
+ }
+
+ // Give the focused view a last chance to handle the dpad key.
+ if (mView.dispatchUnhandledMove(focused, direction)) {
+ return FINISH_HANDLED;
+ }
+ } else {
+ // find the best view to give focus to in this non-touch-mode with no-focus
+ View v = focusSearch(null, direction);
+ if (v != null && v.requestFocus(direction)) {
+ return FINISH_HANDLED;
+ }
}
}
}
+ return FORWARD;
}
- // Not dispatching to IME, continue with post IME actions.
- return deliverGenericMotionEventPostIme(q);
- }
+ private int processPointerEvent(QueuedInputEvent q) {
+ final MotionEvent event = (MotionEvent)q.mEvent;
- private int deliverGenericMotionEventPostIme(QueuedInputEvent q) {
- final MotionEvent event = (MotionEvent) q.mEvent;
- final int source = event.getSource();
- final boolean isJoystick = (source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0;
- final boolean isTouchPad = (source & InputDevice.SOURCE_CLASS_POSITION) != 0;
+ if (mView.dispatchPointerEvent(event)) {
+ return FINISH_HANDLED;
+ }
+ return FORWARD;
+ }
- // If there is no view, then the event will not be handled.
- if (mView == null || !mAdded) {
- if (isJoystick) {
- updateJoystickDirection(event, false);
- } else if (isTouchPad) {
- mSimulatedDpad.updateTouchPad(this, event, false);
+ private int processTrackballEvent(QueuedInputEvent q) {
+ final MotionEvent event = (MotionEvent)q.mEvent;
+
+ if (mView.dispatchTrackballEvent(event)) {
+ return FINISH_HANDLED;
}
- return EVENT_NOT_HANDLED;
+ return FORWARD;
}
- // Deliver the event to the view.
- if (mView.dispatchGenericMotionEvent(event)) {
- if (isJoystick) {
- updateJoystickDirection(event, false);
- } else if (isTouchPad) {
- mSimulatedDpad.updateTouchPad(this, event, false);
+ private int processGenericMotionEvent(QueuedInputEvent q) {
+ final MotionEvent event = (MotionEvent)q.mEvent;
+
+ // Deliver the event to the view.
+ if (mView.dispatchGenericMotionEvent(event)) {
+ return FINISH_HANDLED;
}
- return EVENT_HANDLED;
+ return FORWARD;
}
+ }
- if (isJoystick) {
- // Translate the joystick event into DPAD keys and try to deliver
- // those.
- updateJoystickDirection(event, true);
- return EVENT_HANDLED;
+ /**
+ * Performs synthesis of new input events from unhandled input events.
+ */
+ final class SyntheticInputStage extends InputStage {
+ private final SyntheticTrackballHandler mTrackball = new SyntheticTrackballHandler();
+ private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler();
+ private final SyntheticTouchNavigationHandler mTouchNavigation =
+ new SyntheticTouchNavigationHandler();
+
+ public SyntheticInputStage() {
+ super(null);
+ }
+
+ @Override
+ protected int onProcess(QueuedInputEvent q) {
+ q.mFlags |= QueuedInputEvent.FLAG_RESYNTHESIZED;
+ if (q.mEvent instanceof MotionEvent) {
+ final MotionEvent event = (MotionEvent)q.mEvent;
+ final int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ mTrackball.process(event);
+ return FINISH_HANDLED;
+ } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+ mJoystick.process(event);
+ return FINISH_HANDLED;
+ } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
+ == InputDevice.SOURCE_TOUCH_NAVIGATION) {
+ mTouchNavigation.process(event);
+ return FINISH_HANDLED;
+ }
+ }
+ return FORWARD;
}
- if (isTouchPad) {
- mSimulatedDpad.updateTouchPad(this, event, true);
- return EVENT_HANDLED;
+
+ @Override
+ protected void onDeliverToNext(QueuedInputEvent q) {
+ if ((q.mFlags & QueuedInputEvent.FLAG_RESYNTHESIZED) == 0) {
+ // Cancel related synthetic events if any prior stage has handled the event.
+ if (q.mEvent instanceof MotionEvent) {
+ final MotionEvent event = (MotionEvent)q.mEvent;
+ final int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ mTrackball.cancel(event);
+ } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+ mJoystick.cancel(event);
+ } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
+ == InputDevice.SOURCE_TOUCH_NAVIGATION) {
+ mTouchNavigation.cancel(event);
+ }
+ }
+ }
+ super.onDeliverToNext(q);
}
- return EVENT_NOT_HANDLED;
}
- private void updateJoystickDirection(MotionEvent event, boolean synthesizeNewKeys) {
- final long time = event.getEventTime();
- final int metaState = event.getMetaState();
- final int deviceId = event.getDeviceId();
- final int source = event.getSource();
+ /**
+ * Creates dpad events from unhandled trackball movements.
+ */
+ final class SyntheticTrackballHandler {
+ private final TrackballAxis mX = new TrackballAxis();
+ private final TrackballAxis mY = new TrackballAxis();
+ private long mLastTime;
+
+ public void process(MotionEvent event) {
+ // Translate the trackball event into DPAD keys and try to deliver those.
+ long curTime = SystemClock.uptimeMillis();
+ if ((mLastTime + MAX_TRACKBALL_DELAY) < curTime) {
+ // It has been too long since the last movement,
+ // so restart at the beginning.
+ mX.reset(0);
+ mY.reset(0);
+ mLastTime = curTime;
+ }
+
+ final int action = event.getAction();
+ final int metaState = event.getMetaState();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mX.reset(2);
+ mY.reset(2);
+ enqueueInputEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+ InputDevice.SOURCE_KEYBOARD));
+ break;
+ case MotionEvent.ACTION_UP:
+ mX.reset(2);
+ mY.reset(2);
+ enqueueInputEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+ InputDevice.SOURCE_KEYBOARD));
+ break;
+ }
- int xDirection = joystickAxisValueToDirection(event.getAxisValue(MotionEvent.AXIS_HAT_X));
- if (xDirection == 0) {
- xDirection = joystickAxisValueToDirection(event.getX());
+ if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + mX.position + " step="
+ + mX.step + " dir=" + mX.dir + " acc=" + mX.acceleration
+ + " move=" + event.getX()
+ + " / Y=" + mY.position + " step="
+ + mY.step + " dir=" + mY.dir + " acc=" + mY.acceleration
+ + " move=" + event.getY());
+ final float xOff = mX.collect(event.getX(), event.getEventTime(), "X");
+ final float yOff = mY.collect(event.getY(), event.getEventTime(), "Y");
+
+ // Generate DPAD events based on the trackball movement.
+ // We pick the axis that has moved the most as the direction of
+ // the DPAD. When we generate DPAD events for one axis, then the
+ // other axis is reset -- we don't want to perform DPAD jumps due
+ // to slight movements in the trackball when making major movements
+ // along the other axis.
+ int keycode = 0;
+ int movement = 0;
+ float accel = 1;
+ if (xOff > yOff) {
+ movement = mX.generate();
+ if (movement != 0) {
+ keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT
+ : KeyEvent.KEYCODE_DPAD_LEFT;
+ accel = mX.acceleration;
+ mY.reset(2);
+ }
+ } else if (yOff > 0) {
+ movement = mY.generate();
+ if (movement != 0) {
+ keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN
+ : KeyEvent.KEYCODE_DPAD_UP;
+ accel = mY.acceleration;
+ mX.reset(2);
+ }
+ }
+
+ if (keycode != 0) {
+ if (movement < 0) movement = -movement;
+ int accelMovement = (int)(movement * accel);
+ if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement
+ + " accelMovement=" + accelMovement
+ + " accel=" + accel);
+ if (accelMovement > movement) {
+ if (DEBUG_TRACKBALL) Log.v(TAG, "Delivering fake DPAD: "
+ + keycode);
+ movement--;
+ int repeatCount = accelMovement - movement;
+ enqueueInputEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_MULTIPLE, keycode, repeatCount, metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+ InputDevice.SOURCE_KEYBOARD));
+ }
+ while (movement > 0) {
+ if (DEBUG_TRACKBALL) Log.v(TAG, "Delivering fake DPAD: "
+ + keycode);
+ movement--;
+ curTime = SystemClock.uptimeMillis();
+ enqueueInputEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_DOWN, keycode, 0, metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+ InputDevice.SOURCE_KEYBOARD));
+ enqueueInputEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_UP, keycode, 0, metaState,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
+ InputDevice.SOURCE_KEYBOARD));
+ }
+ mLastTime = curTime;
+ }
}
- int yDirection = joystickAxisValueToDirection(event.getAxisValue(MotionEvent.AXIS_HAT_Y));
- if (yDirection == 0) {
- yDirection = joystickAxisValueToDirection(event.getY());
+ public void cancel(MotionEvent event) {
+ mLastTime = Integer.MIN_VALUE;
+
+ // If we reach this, we consumed a trackball event.
+ // Because we will not translate the trackball event into a key event,
+ // touch mode will not exit, so we exit touch mode here.
+ if (mView != null && mAdded) {
+ ensureTouchMode(false);
+ }
}
+ }
- if (xDirection != mLastJoystickXDirection) {
- if (mLastJoystickXKeyCode != 0) {
- enqueueInputEvent(new KeyEvent(time, time,
- KeyEvent.ACTION_UP, mLastJoystickXKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
- mLastJoystickXKeyCode = 0;
+ /**
+ * Maintains state information for a single trackball axis, generating
+ * discrete (DPAD) movements based on raw trackball motion.
+ */
+ static final class TrackballAxis {
+ /**
+ * The maximum amount of acceleration we will apply.
+ */
+ static final float MAX_ACCELERATION = 20;
+
+ /**
+ * The maximum amount of time (in milliseconds) between events in order
+ * for us to consider the user to be doing fast trackball movements,
+ * and thus apply an acceleration.
+ */
+ static final long FAST_MOVE_TIME = 150;
+
+ /**
+ * Scaling factor to the time (in milliseconds) between events to how
+ * much to multiple/divide the current acceleration. When movement
+ * is < FAST_MOVE_TIME this multiplies the acceleration; when >
+ * FAST_MOVE_TIME it divides it.
+ */
+ static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40);
+
+ static final float FIRST_MOVEMENT_THRESHOLD = 0.5f;
+ static final float SECOND_CUMULATIVE_MOVEMENT_THRESHOLD = 2.0f;
+ static final float SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD = 1.0f;
+
+ float position;
+ float acceleration = 1;
+ long lastMoveTime = 0;
+ int step;
+ int dir;
+ int nonAccelMovement;
+
+ void reset(int _step) {
+ position = 0;
+ acceleration = 1;
+ lastMoveTime = 0;
+ step = _step;
+ dir = 0;
+ }
+
+ /**
+ * Add trackball movement into the state. If the direction of movement
+ * has been reversed, the state is reset before adding the
+ * movement (so that you don't have to compensate for any previously
+ * collected movement before see the result of the movement in the
+ * new direction).
+ *
+ * @return Returns the absolute value of the amount of movement
+ * collected so far.
+ */
+ float collect(float off, long time, String axis) {
+ long normTime;
+ if (off > 0) {
+ normTime = (long)(off * FAST_MOVE_TIME);
+ if (dir < 0) {
+ if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!");
+ position = 0;
+ step = 0;
+ acceleration = 1;
+ lastMoveTime = 0;
+ }
+ dir = 1;
+ } else if (off < 0) {
+ normTime = (long)((-off) * FAST_MOVE_TIME);
+ if (dir > 0) {
+ if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!");
+ position = 0;
+ step = 0;
+ acceleration = 1;
+ lastMoveTime = 0;
+ }
+ dir = -1;
+ } else {
+ normTime = 0;
}
- mLastJoystickXDirection = xDirection;
+ // The number of milliseconds between each movement that is
+ // considered "normal" and will not result in any acceleration
+ // or deceleration, scaled by the offset we have here.
+ if (normTime > 0) {
+ long delta = time - lastMoveTime;
+ lastMoveTime = time;
+ float acc = acceleration;
+ if (delta < normTime) {
+ // The user is scrolling rapidly, so increase acceleration.
+ float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR;
+ if (scale > 1) acc *= scale;
+ if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off="
+ + off + " normTime=" + normTime + " delta=" + delta
+ + " scale=" + scale + " acc=" + acc);
+ acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION;
+ } else {
+ // The user is scrolling slowly, so decrease acceleration.
+ float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR;
+ if (scale > 1) acc /= scale;
+ if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off="
+ + off + " normTime=" + normTime + " delta=" + delta
+ + " scale=" + scale + " acc=" + acc);
+ acceleration = acc > 1 ? acc : 1;
+ }
+ }
+ position += off;
+ return Math.abs(position);
+ }
+
+ /**
+ * Generate the number of discrete movement events appropriate for
+ * the currently collected trackball movement.
+ *
+ * @return Returns the number of discrete movements, either positive
+ * or negative, or 0 if there is not enough trackball movement yet
+ * for a discrete movement.
+ */
+ int generate() {
+ int movement = 0;
+ nonAccelMovement = 0;
+ do {
+ final int dir = position >= 0 ? 1 : -1;
+ switch (step) {
+ // If we are going to execute the first step, then we want
+ // to do this as soon as possible instead of waiting for
+ // a full movement, in order to make things look responsive.
+ case 0:
+ if (Math.abs(position) < FIRST_MOVEMENT_THRESHOLD) {
+ return movement;
+ }
+ movement += dir;
+ nonAccelMovement += dir;
+ step = 1;
+ break;
+ // If we have generated the first movement, then we need
+ // to wait for the second complete trackball motion before
+ // generating the second discrete movement.
+ case 1:
+ if (Math.abs(position) < SECOND_CUMULATIVE_MOVEMENT_THRESHOLD) {
+ return movement;
+ }
+ movement += dir;
+ nonAccelMovement += dir;
+ position -= SECOND_CUMULATIVE_MOVEMENT_THRESHOLD * dir;
+ step = 2;
+ break;
+ // After the first two, we generate discrete movements
+ // consistently with the trackball, applying an acceleration
+ // if the trackball is moving quickly. This is a simple
+ // acceleration on top of what we already compute based
+ // on how quickly the wheel is being turned, to apply
+ // a longer increasing acceleration to continuous movement
+ // in one direction.
+ default:
+ if (Math.abs(position) < SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD) {
+ return movement;
+ }
+ movement += dir;
+ position -= dir * SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD;
+ float acc = acceleration;
+ acc *= 1.1f;
+ acceleration = acc < MAX_ACCELERATION ? acc : acceleration;
+ break;
+ }
+ } while (true);
+ }
+ }
+
+ /**
+ * Creates dpad events from unhandled joystick movements.
+ */
+ final class SyntheticJoystickHandler extends Handler {
+ private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 1;
+ private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 2;
- if (xDirection != 0 && synthesizeNewKeys) {
- mLastJoystickXKeyCode = xDirection > 0
- ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
- enqueueInputEvent(new KeyEvent(time, time,
- KeyEvent.ACTION_DOWN, mLastJoystickXKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+ private int mLastXDirection;
+ private int mLastYDirection;
+ private int mLastXKeyCode;
+ private int mLastYKeyCode;
+
+ public SyntheticJoystickHandler() {
+ super(true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
+ case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
+ KeyEvent oldEvent = (KeyEvent)msg.obj;
+ KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
+ SystemClock.uptimeMillis(),
+ oldEvent.getRepeatCount() + 1);
+ if (mAttachInfo.mHasWindowFocus) {
+ enqueueInputEvent(e);
+ Message m = obtainMessage(msg.what, e);
+ m.setAsynchronous(true);
+ sendMessageDelayed(m, ViewConfiguration.getKeyRepeatDelay());
+ }
+ } break;
}
}
- if (yDirection != mLastJoystickYDirection) {
- if (mLastJoystickYKeyCode != 0) {
- enqueueInputEvent(new KeyEvent(time, time,
- KeyEvent.ACTION_UP, mLastJoystickYKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
- mLastJoystickYKeyCode = 0;
+ public void process(MotionEvent event) {
+ update(event, true);
+ }
+
+ public void cancel(MotionEvent event) {
+ update(event, false);
+ }
+
+ private void update(MotionEvent event, boolean synthesizeNewKeys) {
+ final long time = event.getEventTime();
+ final int metaState = event.getMetaState();
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
+
+ int xDirection = joystickAxisValueToDirection(
+ event.getAxisValue(MotionEvent.AXIS_HAT_X));
+ if (xDirection == 0) {
+ xDirection = joystickAxisValueToDirection(event.getX());
+ }
+
+ int yDirection = joystickAxisValueToDirection(
+ event.getAxisValue(MotionEvent.AXIS_HAT_Y));
+ if (yDirection == 0) {
+ yDirection = joystickAxisValueToDirection(event.getY());
}
- mLastJoystickYDirection = yDirection;
+ if (xDirection != mLastXDirection) {
+ if (mLastXKeyCode != 0) {
+ removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
+ enqueueInputEvent(new KeyEvent(time, time,
+ KeyEvent.ACTION_UP, mLastXKeyCode, 0, metaState,
+ deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+ mLastXKeyCode = 0;
+ }
- if (yDirection != 0 && synthesizeNewKeys) {
- mLastJoystickYKeyCode = yDirection > 0
- ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
- enqueueInputEvent(new KeyEvent(time, time,
- KeyEvent.ACTION_DOWN, mLastJoystickYKeyCode, 0, metaState,
- deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+ mLastXDirection = xDirection;
+
+ if (xDirection != 0 && synthesizeNewKeys) {
+ mLastXKeyCode = xDirection > 0
+ ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
+ final KeyEvent e = new KeyEvent(time, time,
+ KeyEvent.ACTION_DOWN, mLastXKeyCode, 0, metaState,
+ deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
+ enqueueInputEvent(e);
+ Message m = obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e);
+ m.setAsynchronous(true);
+ sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+ }
+ }
+
+ if (yDirection != mLastYDirection) {
+ if (mLastYKeyCode != 0) {
+ removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
+ enqueueInputEvent(new KeyEvent(time, time,
+ KeyEvent.ACTION_UP, mLastYKeyCode, 0, metaState,
+ deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
+ mLastYKeyCode = 0;
+ }
+
+ mLastYDirection = yDirection;
+
+ if (yDirection != 0 && synthesizeNewKeys) {
+ mLastYKeyCode = yDirection > 0
+ ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
+ final KeyEvent e = new KeyEvent(time, time,
+ KeyEvent.ACTION_DOWN, mLastYKeyCode, 0, metaState,
+ deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
+ enqueueInputEvent(e);
+ Message m = obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e);
+ m.setAsynchronous(true);
+ sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+ }
+ }
+ }
+
+ private int joystickAxisValueToDirection(float value) {
+ if (value >= 0.5f) {
+ return 1;
+ } else if (value <= -0.5f) {
+ return -1;
+ } else {
+ return 0;
}
}
}
- private static int joystickAxisValueToDirection(float value) {
- if (value >= 0.5f) {
- return 1;
- } else if (value <= -0.5f) {
- return -1;
- } else {
- return 0;
+ /**
+ * Creates dpad events from unhandled touch navigation movements.
+ */
+ final class SyntheticTouchNavigationHandler extends Handler {
+ private static final String LOCAL_TAG = "SyntheticTouchNavigationHandler";
+ private static final boolean LOCAL_DEBUG = false;
+
+ // Assumed nominal width and height in millimeters of a touch navigation pad,
+ // if no resolution information is available from the input system.
+ private static final float DEFAULT_WIDTH_MILLIMETERS = 48;
+ private static final float DEFAULT_HEIGHT_MILLIMETERS = 48;
+
+ /* TODO: These constants should eventually be moved to ViewConfiguration. */
+
+ // Tap timeout in milliseconds.
+ private static final int TAP_TIMEOUT = 250;
+
+ // The maximum distance traveled for a gesture to be considered a tap in millimeters.
+ private static final int TAP_SLOP_MILLIMETERS = 5;
+
+ // The nominal distance traveled to move by one unit.
+ private static final int TICK_DISTANCE_MILLIMETERS = 12;
+
+ // Minimum and maximum fling velocity in ticks per second.
+ // The minimum velocity should be set such that we perform enough ticks per
+ // second that the fling appears to be fluid. For example, if we set the minimum
+ // to 2 ticks per second, then there may be up to half a second delay between the next
+ // to last and last ticks which is noticeably discrete and jerky. This value should
+ // probably not be set to anything less than about 4.
+ // If fling accuracy is a problem then consider tuning the tick distance instead.
+ private static final float MIN_FLING_VELOCITY_TICKS_PER_SECOND = 6f;
+ private static final float MAX_FLING_VELOCITY_TICKS_PER_SECOND = 20f;
+
+ // Fling velocity decay factor applied after each new key is emitted.
+ // This parameter controls the deceleration and overall duration of the fling.
+ // The fling stops automatically when its velocity drops below the minimum
+ // fling velocity defined above.
+ private static final float FLING_TICK_DECAY = 0.8f;
+
+ /* The input device that we are tracking. */
+
+ private int mCurrentDeviceId = -1;
+ private int mCurrentSource;
+ private boolean mCurrentDeviceSupported;
+
+ /* Configuration for the current input device. */
+
+ // The tap timeout and scaled slop.
+ private int mConfigTapTimeout;
+ private float mConfigTapSlop;
+
+ // The scaled tick distance. A movement of this amount should generally translate
+ // into a single dpad event in a given direction.
+ private float mConfigTickDistance;
+
+ // The minimum and maximum scaled fling velocity.
+ private float mConfigMinFlingVelocity;
+ private float mConfigMaxFlingVelocity;
+
+ /* Tracking state. */
+
+ // The velocity tracker for detecting flings.
+ private VelocityTracker mVelocityTracker;
+
+ // The active pointer id, or -1 if none.
+ private int mActivePointerId = -1;
+
+ // Time and location where tracking started.
+ private long mStartTime;
+ private float mStartX;
+ private float mStartY;
+
+ // Most recently observed position.
+ private float mLastX;
+ private float mLastY;
+
+ // Accumulated movement delta since the last direction key was sent.
+ private float mAccumulatedX;
+ private float mAccumulatedY;
+
+ // Set to true if any movement was delivered to the app.
+ // Implies that tap slop was exceeded.
+ private boolean mConsumedMovement;
+
+ // The most recently sent key down event.
+ // The keycode remains set until the direction changes or a fling ends
+ // so that repeated key events may be generated as required.
+ private long mPendingKeyDownTime;
+ private int mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+ private int mPendingKeyRepeatCount;
+ private int mPendingKeyMetaState;
+
+ // The current fling velocity while a fling is in progress.
+ private boolean mFlinging;
+ private float mFlingVelocity;
+
+ public SyntheticTouchNavigationHandler() {
+ super(true);
+ }
+
+ public void process(MotionEvent event) {
+ // Update the current device information.
+ final long time = event.getEventTime();
+ final int deviceId = event.getDeviceId();
+ final int source = event.getSource();
+ if (mCurrentDeviceId != deviceId || mCurrentSource != source) {
+ finishKeys(time);
+ finishTracking(time);
+ mCurrentDeviceId = deviceId;
+ mCurrentSource = source;
+ mCurrentDeviceSupported = false;
+ InputDevice device = event.getDevice();
+ if (device != null) {
+ // In order to support an input device, we must know certain
+ // characteristics about it, such as its size and resolution.
+ InputDevice.MotionRange xRange = device.getMotionRange(MotionEvent.AXIS_X);
+ InputDevice.MotionRange yRange = device.getMotionRange(MotionEvent.AXIS_Y);
+ if (xRange != null && yRange != null) {
+ mCurrentDeviceSupported = true;
+
+ // Infer the resolution if it not actually known.
+ float xRes = xRange.getResolution();
+ if (xRes <= 0) {
+ xRes = xRange.getRange() / DEFAULT_WIDTH_MILLIMETERS;
+ }
+ float yRes = yRange.getResolution();
+ if (yRes <= 0) {
+ yRes = yRange.getRange() / DEFAULT_HEIGHT_MILLIMETERS;
+ }
+ float nominalRes = (xRes + yRes) * 0.5f;
+
+ // Precompute all of the configuration thresholds we will need.
+ mConfigTapTimeout = TAP_TIMEOUT;
+ mConfigTapSlop = TAP_SLOP_MILLIMETERS * nominalRes;
+ mConfigTickDistance = TICK_DISTANCE_MILLIMETERS * nominalRes;
+ mConfigMinFlingVelocity =
+ MIN_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
+ mConfigMaxFlingVelocity =
+ MAX_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance;
+
+ if (LOCAL_DEBUG) {
+ Log.d(LOCAL_TAG, "Configured device " + mCurrentDeviceId
+ + " (" + Integer.toHexString(mCurrentSource) + "): "
+ + "mConfigTapTimeout=" + mConfigTapTimeout
+ + ", mConfigTapSlop=" + mConfigTapSlop
+ + ", mConfigTickDistance=" + mConfigTickDistance
+ + ", mConfigMinFlingVelocity=" + mConfigMinFlingVelocity
+ + ", mConfigMaxFlingVelocity=" + mConfigMaxFlingVelocity);
+ }
+ }
+ }
+ }
+ if (!mCurrentDeviceSupported) {
+ return;
+ }
+
+ // Handle the event.
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ boolean caughtFling = mFlinging;
+ finishKeys(time);
+ finishTracking(time);
+ mActivePointerId = event.getPointerId(0);
+ mVelocityTracker = VelocityTracker.obtain();
+ mVelocityTracker.addMovement(event);
+ mStartTime = time;
+ mStartX = event.getX();
+ mStartY = event.getY();
+ mLastX = mStartX;
+ mLastY = mStartY;
+ mAccumulatedX = 0;
+ mAccumulatedY = 0;
+
+ // If we caught a fling, then pretend that the tap slop has already
+ // been exceeded to suppress taps whose only purpose is to stop the fling.
+ mConsumedMovement = caughtFling;
+ break;
+ }
+
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_UP: {
+ if (mActivePointerId < 0) {
+ break;
+ }
+ final int index = event.findPointerIndex(mActivePointerId);
+ if (index < 0) {
+ finishKeys(time);
+ finishTracking(time);
+ break;
+ }
+
+ mVelocityTracker.addMovement(event);
+ final float x = event.getX(index);
+ final float y = event.getY(index);
+ mAccumulatedX += x - mLastX;
+ mAccumulatedY += y - mLastY;
+ mLastX = x;
+ mLastY = y;
+
+ // Consume any accumulated movement so far.
+ final int metaState = event.getMetaState();
+ consumeAccumulatedMovement(time, metaState);
+
+ // Detect taps and flings.
+ if (action == MotionEvent.ACTION_UP) {
+ if (!mConsumedMovement
+ && Math.hypot(mLastX - mStartX, mLastY - mStartY) < mConfigTapSlop
+ && time <= mStartTime + mConfigTapTimeout) {
+ // It's a tap!
+ finishKeys(time);
+ sendKeyDownOrRepeat(time, KeyEvent.KEYCODE_DPAD_CENTER, metaState);
+ sendKeyUp(time);
+ } else if (mConsumedMovement
+ && mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
+ // It might be a fling.
+ mVelocityTracker.computeCurrentVelocity(1000, mConfigMaxFlingVelocity);
+ final float vx = mVelocityTracker.getXVelocity(mActivePointerId);
+ final float vy = mVelocityTracker.getYVelocity(mActivePointerId);
+ if (!startFling(time, vx, vy)) {
+ finishKeys(time);
+ }
+ }
+ finishTracking(time);
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL: {
+ finishKeys(time);
+ finishTracking(time);
+ break;
+ }
+ }
+ }
+
+ public void cancel(MotionEvent event) {
+ if (mCurrentDeviceId == event.getDeviceId()
+ && mCurrentSource == event.getSource()) {
+ final long time = event.getEventTime();
+ finishKeys(time);
+ finishTracking(time);
+ }
}
+
+ private void finishKeys(long time) {
+ cancelFling();
+ sendKeyUp(time);
+ }
+
+ private void finishTracking(long time) {
+ if (mActivePointerId >= 0) {
+ mActivePointerId = -1;
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private void consumeAccumulatedMovement(long time, int metaState) {
+ final float absX = Math.abs(mAccumulatedX);
+ final float absY = Math.abs(mAccumulatedY);
+ if (absX >= absY) {
+ if (absX >= mConfigTickDistance) {
+ mAccumulatedX = consumeAccumulatedMovement(time, metaState, mAccumulatedX,
+ KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT);
+ mAccumulatedY = 0;
+ mConsumedMovement = true;
+ }
+ } else {
+ if (absY >= mConfigTickDistance) {
+ mAccumulatedY = consumeAccumulatedMovement(time, metaState, mAccumulatedY,
+ KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN);
+ mAccumulatedX = 0;
+ mConsumedMovement = true;
+ }
+ }
+ }
+
+ private float consumeAccumulatedMovement(long time, int metaState,
+ float accumulator, int negativeKeyCode, int positiveKeyCode) {
+ while (accumulator <= -mConfigTickDistance) {
+ sendKeyDownOrRepeat(time, negativeKeyCode, metaState);
+ accumulator += mConfigTickDistance;
+ }
+ while (accumulator >= mConfigTickDistance) {
+ sendKeyDownOrRepeat(time, positiveKeyCode, metaState);
+ accumulator -= mConfigTickDistance;
+ }
+ return accumulator;
+ }
+
+ private void sendKeyDownOrRepeat(long time, int keyCode, int metaState) {
+ if (mPendingKeyCode != keyCode) {
+ sendKeyUp(time);
+ mPendingKeyDownTime = time;
+ mPendingKeyCode = keyCode;
+ mPendingKeyRepeatCount = 0;
+ } else {
+ mPendingKeyRepeatCount += 1;
+ }
+ mPendingKeyMetaState = metaState;
+
+ // Note: Normally we would pass FLAG_LONG_PRESS when the repeat count is 1
+ // but it doesn't quite make sense when simulating the events in this way.
+ if (LOCAL_DEBUG) {
+ Log.d(LOCAL_TAG, "Sending key down: keyCode=" + mPendingKeyCode
+ + ", repeatCount=" + mPendingKeyRepeatCount
+ + ", metaState=" + Integer.toHexString(mPendingKeyMetaState));
+ }
+ enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time,
+ KeyEvent.ACTION_DOWN, mPendingKeyCode, mPendingKeyRepeatCount,
+ mPendingKeyMetaState, mCurrentDeviceId,
+ KeyEvent.FLAG_FALLBACK, mCurrentSource));
+ }
+
+ private void sendKeyUp(long time) {
+ if (mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) {
+ if (LOCAL_DEBUG) {
+ Log.d(LOCAL_TAG, "Sending key up: keyCode=" + mPendingKeyCode
+ + ", metaState=" + Integer.toHexString(mPendingKeyMetaState));
+ }
+ enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time,
+ KeyEvent.ACTION_UP, mPendingKeyCode, 0, mPendingKeyMetaState,
+ mCurrentDeviceId, 0, KeyEvent.FLAG_FALLBACK,
+ mCurrentSource));
+ mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN;
+ }
+ }
+
+ private boolean startFling(long time, float vx, float vy) {
+ if (LOCAL_DEBUG) {
+ Log.d(LOCAL_TAG, "Considering fling: vx=" + vx + ", vy=" + vy
+ + ", min=" + mConfigMinFlingVelocity);
+ }
+
+ // Flings must be oriented in the same direction as the preceding movements.
+ switch (mPendingKeyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (-vx >= mConfigMinFlingVelocity
+ && Math.abs(vy) < mConfigMinFlingVelocity) {
+ mFlingVelocity = -vx;
+ break;
+ }
+ return false;
+
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (vx >= mConfigMinFlingVelocity
+ && Math.abs(vy) < mConfigMinFlingVelocity) {
+ mFlingVelocity = vx;
+ break;
+ }
+ return false;
+
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (-vy >= mConfigMinFlingVelocity
+ && Math.abs(vx) < mConfigMinFlingVelocity) {
+ mFlingVelocity = -vy;
+ break;
+ }
+ return false;
+
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (vy >= mConfigMinFlingVelocity
+ && Math.abs(vx) < mConfigMinFlingVelocity) {
+ mFlingVelocity = vy;
+ break;
+ }
+ return false;
+ }
+
+ // Post the first fling event.
+ mFlinging = postFling(time);
+ return mFlinging;
+ }
+
+ private boolean postFling(long time) {
+ // The idea here is to estimate the time when the pointer would have
+ // traveled one tick distance unit given the current fling velocity.
+ // This effect creates continuity of motion.
+ if (mFlingVelocity >= mConfigMinFlingVelocity) {
+ long delay = (long)(mConfigTickDistance / mFlingVelocity * 1000);
+ postAtTime(mFlingRunnable, time + delay);
+ if (LOCAL_DEBUG) {
+ Log.d(LOCAL_TAG, "Posted fling: velocity="
+ + mFlingVelocity + ", delay=" + delay
+ + ", keyCode=" + mPendingKeyCode);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void cancelFling() {
+ if (mFlinging) {
+ removeCallbacks(mFlingRunnable);
+ mFlinging = false;
+ }
+ }
+
+ private final Runnable mFlingRunnable = new Runnable() {
+ @Override
+ public void run() {
+ final long time = SystemClock.uptimeMillis();
+ sendKeyDownOrRepeat(time, mPendingKeyCode, mPendingKeyMetaState);
+ mFlingVelocity *= FLING_TICK_DECAY;
+ if (!postFling(time)) {
+ mFlinging = false;
+ finishKeys(time);
+ }
+ }
+ };
}
/**
@@ -3621,150 +4861,6 @@ public final class ViewRootImpl implements ViewParent,
return false;
}
- private int deliverKeyEvent(QueuedInputEvent q) {
- final KeyEvent event = (KeyEvent)q.mEvent;
- if (mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onKeyEvent(event, 0);
- }
-
- 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.
- if (mView.dispatchKeyEventPreIme(event)) {
- return EVENT_HANDLED;
- }
-
- // 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 key event to IME: seq="
- + seq + " event=" + event);
- int result = imm.dispatchKeyEvent(mView.getContext(), seq, event,
- mInputMethodCallback);
- if (result != EVENT_NOT_HANDLED) {
- return result;
- }
- }
- }
- }
-
- // Not dispatching to IME, continue with post IME actions.
- return deliverKeyEventPostIme(q);
- }
-
- private int deliverKeyEventPostIme(QueuedInputEvent q) {
- final KeyEvent event = (KeyEvent)q.mEvent;
-
- // If the view went away, then the event will not be handled.
- if (mView == null || !mAdded) {
- return EVENT_NOT_HANDLED;
- }
-
- // If the key's purpose is to exit touch mode then we consume it and consider it handled.
- if (checkForLeavingTouchModeAndConsume(event)) {
- return EVENT_HANDLED;
- }
-
- // Make sure the fallback event policy sees all keys that will be delivered to the
- // view hierarchy.
- mFallbackEventHandler.preDispatchKeyEvent(event);
-
- // Deliver the key to the view hierarchy.
- if (mView.dispatchKeyEvent(event)) {
- return EVENT_HANDLED;
- }
-
- // If the Control modifier is held, try to interpret the key as a shortcut.
- if (event.getAction() == KeyEvent.ACTION_DOWN
- && event.isCtrlPressed()
- && event.getRepeatCount() == 0
- && !KeyEvent.isModifierKey(event.getKeyCode())) {
- if (mView.dispatchKeyShortcutEvent(event)) {
- return EVENT_HANDLED;
- }
- }
-
- // Apply the fallback event policy.
- if (mFallbackEventHandler.dispatchKeyEvent(event)) {
- return EVENT_HANDLED;
- }
-
- // Handle automatic focus changes.
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- int direction = 0;
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_LEFT;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_RIGHT;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_UP;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_DOWN;
- }
- break;
- case KeyEvent.KEYCODE_TAB:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_FORWARD;
- } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
- direction = View.FOCUS_BACKWARD;
- }
- break;
- }
- if (direction != 0) {
- View focused = mView.findFocus();
- if (focused != null) {
- View v = focused.focusSearch(direction);
- if (v != null && v != focused) {
- // do the math the get the interesting rect
- // of previous focused into the coord system of
- // newly focused view
- focused.getFocusedRect(mTempRect);
- if (mView instanceof ViewGroup) {
- ((ViewGroup) mView).offsetDescendantRectToMyCoords(
- focused, mTempRect);
- ((ViewGroup) mView).offsetRectIntoDescendantCoords(
- v, mTempRect);
- }
- if (v.requestFocus(direction, mTempRect)) {
- playSoundEffect(SoundEffectConstants
- .getContantForFocusDirection(direction));
- return EVENT_HANDLED;
- }
- }
-
- // Give the focused view a last chance to handle the dpad key.
- if (mView.dispatchUnhandledMove(focused, direction)) {
- return EVENT_HANDLED;
- }
- } else {
- // find the best view to give focus to in this non-touch-mode with no-focus
- View v = focusSearch(null, direction);
- if (v != null && v.requestFocus(direction)) {
- return EVENT_HANDLED;
- }
- }
- }
- }
-
- // Key was unhandled.
- return EVENT_NOT_HANDLED;
- }
-
/* drag/drop */
void setLocalDragState(Object obj) {
mLocalDragState = obj;
@@ -3874,7 +4970,7 @@ public final class ViewRootImpl implements ViewParent,
public void handleDispatchDoneAnimating() {
if (mWindowsAnimating) {
mWindowsAnimating = false;
- if (!mDirty.isEmpty() || mIsAnimating) {
+ if (!mDirty.isEmpty() || mIsAnimating || mFullRedrawNeeded) {
scheduleTraversals();
}
}
@@ -3940,7 +5036,7 @@ public final class ViewRootImpl implements ViewParent,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f),
viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
- mWinFrame, mPendingContentInsets, mPendingVisibleInsets,
+ mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingConfiguration, mSurface);
//Log.d(TAG, "<<<<<< BACK FROM relayout");
if (restore) {
@@ -3949,6 +5045,7 @@ public final class ViewRootImpl implements ViewParent,
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWinFrame(mWinFrame);
+ mTranslator.translateRectInScreenToAppWindow(mPendingOverscanInsets);
mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets);
mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets);
}
@@ -4066,6 +5163,7 @@ public final class ViewRootImpl implements ViewParent,
}
if (mAdded && !mFirst) {
+ invalidateDisplayLists();
destroyHardwareRenderer();
if (mView != null) {
@@ -4098,14 +5196,30 @@ public final class ViewRootImpl implements ViewParent,
}
public void loadSystemProperties() {
- boolean layout = SystemProperties.getBoolean(
- View.DEBUG_LAYOUT_PROPERTY, false);
- if (layout != mAttachInfo.mDebugLayout) {
- mAttachInfo.mDebugLayout = layout;
- if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) {
- mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Profiling
+ mProfileRendering = SystemProperties.getBoolean(PROPERTY_PROFILE_RENDERING, false);
+ profileRendering(mAttachInfo.mHasWindowFocus);
+
+ // Hardware rendering
+ if (mAttachInfo.mHardwareRenderer != null) {
+ if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mHolder.getSurface())) {
+ invalidate();
+ }
+ }
+
+ // Layout debugging
+ boolean layout = SystemProperties.getBoolean(View.DEBUG_LAYOUT_PROPERTY, false);
+ if (layout != mAttachInfo.mDebugLayout) {
+ mAttachInfo.mDebugLayout = layout;
+ if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) {
+ mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200);
+ }
+ }
}
- }
+ });
}
private void destroyHardwareRenderer() {
@@ -4124,20 +5238,12 @@ public final class ViewRootImpl implements ViewParent,
}
}
- void dispatchImeFinishedEvent(int seq, boolean handled) {
- Message msg = mHandler.obtainMessage(MSG_IME_FINISHED_EVENT);
- msg.arg1 = seq;
- msg.arg2 = handled ? 1 : 0;
- msg.setAsynchronous(true);
- mHandler.sendMessage(msg);
- }
-
public void dispatchFinishInputConnection(InputConnection connection) {
Message msg = mHandler.obtainMessage(MSG_FINISH_INPUT_CONNECTION, connection);
mHandler.sendMessage(msg);
}
- public void dispatchResized(Rect frame, Rect contentInsets,
+ public void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": frame=" + frame.toShortString()
+ " contentInsets=" + contentInsets.toShortString()
@@ -4146,6 +5252,7 @@ public final class ViewRootImpl implements ViewParent,
Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(frame);
+ mTranslator.translateRectInScreenToAppWindow(overscanInsets);
mTranslator.translateRectInScreenToAppWindow(contentInsets);
mTranslator.translateRectInScreenToAppWindow(visibleInsets);
}
@@ -4155,6 +5262,7 @@ public final class ViewRootImpl implements ViewParent,
args.arg2 = sameProcessCall ? new Rect(contentInsets) : contentInsets;
args.arg3 = sameProcessCall ? new Rect(visibleInsets) : visibleInsets;
args.arg4 = sameProcessCall && newConfig != null ? new Configuration(newConfig) : newConfig;
+ args.arg5 = sameProcessCall ? new Rect(overscanInsets) : overscanInsets;
msg.obj = args;
mHandler.sendMessage(msg);
}
@@ -4184,13 +5292,25 @@ public final class ViewRootImpl implements ViewParent,
* needing a queue on the application's side.
*/
private static final class QueuedInputEvent {
- public static final int FLAG_DELIVER_POST_IME = 1;
+ public static final int FLAG_DELIVER_POST_IME = 1 << 0;
+ public static final int FLAG_DEFERRED = 1 << 1;
+ public static final int FLAG_FINISHED = 1 << 2;
+ public static final int FLAG_FINISHED_HANDLED = 1 << 3;
+ public static final int FLAG_RESYNTHESIZED = 1 << 4;
public QueuedInputEvent mNext;
public InputEvent mEvent;
public InputEventReceiver mReceiver;
public int mFlags;
+
+ public boolean shouldSkipIme() {
+ if ((mFlags & FLAG_DELIVER_POST_IME) != 0) {
+ return true;
+ }
+ return mEvent instanceof MotionEvent
+ && mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER);
+ }
}
private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
@@ -4234,15 +5354,17 @@ public final class ViewRootImpl implements ViewParent,
// in response to touch events and we want to ensure that the injected keys
// are processed in the order they were received and we cannot trust that
// the time stamp of injected events are monotonic.
- QueuedInputEvent last = mFirstPendingInputEvent;
+ QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
- mFirstPendingInputEvent = q;
+ mPendingInputEventHead = q;
+ mPendingInputEventTail = q;
} else {
- while (last.mNext != null) {
- last = last.mNext;
- }
last.mNext = q;
+ mPendingInputEventTail = q;
}
+ mPendingInputEventCount += 1;
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
+ mPendingInputEventCount);
if (processImmediately) {
doProcessInputEvents();
@@ -4261,16 +5383,20 @@ public final class ViewRootImpl implements ViewParent,
}
void doProcessInputEvents() {
- while (mCurrentInputEvent == null && mFirstPendingInputEvent != null) {
- QueuedInputEvent q = mFirstPendingInputEvent;
- mFirstPendingInputEvent = q.mNext;
+ // Deliver all pending input events in the queue.
+ while (mPendingInputEventHead != null) {
+ QueuedInputEvent q = mPendingInputEventHead;
+ mPendingInputEventHead = q.mNext;
+ if (mPendingInputEventHead == null) {
+ mPendingInputEventTail = null;
+ }
q.mNext = null;
- mCurrentInputEvent = q;
- final int result = deliverInputEvent(q);
- if (result != EVENT_IN_PROGRESS) {
- finishCurrentInputEvent(result == EVENT_HANDLED);
- }
+ mPendingInputEventCount -= 1;
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
+ mPendingInputEventCount);
+
+ deliverInputEvent(q);
}
// We are done processing all input events that we can process right now
@@ -4281,44 +5407,27 @@ public final class ViewRootImpl implements ViewParent,
}
}
- void handleImeFinishedEvent(int seq, boolean handled) {
- final QueuedInputEvent q = mCurrentInputEvent;
- if (q != null && q.mEvent.getSequenceNumber() == seq) {
- if (DEBUG_IMF) {
- Log.v(TAG, "IME finished event: seq=" + seq
- + " handled=" + handled + " event=" + q);
- }
-
- if (!handled) {
- // 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 && !isTerminalInputEvent(q.mEvent)) {
- Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent);
- } else {
- final int result = deliverInputEventPostIme(q);
- if (result == EVENT_HANDLED) {
- handled = true;
- }
- }
+ private void deliverInputEvent(QueuedInputEvent q) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent");
+ try {
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
}
- finishCurrentInputEvent(handled);
- // Immediately start processing the next input event.
- doProcessInputEvents();
- } else {
- if (DEBUG_IMF) {
- Log.v(TAG, "IME finished event: seq=" + seq
- + " handled=" + handled + ", event not found!");
+ InputStage stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
+ if (stage != null) {
+ stage.deliver(q);
+ } else {
+ finishInputEvent(q);
}
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
- private void finishCurrentInputEvent(boolean handled) {
- final QueuedInputEvent q = mCurrentInputEvent;
- mCurrentInputEvent = null;
-
+ private void finishInputEvent(QueuedInputEvent q) {
if (q.mReceiver != null) {
+ boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
q.mReceiver.finishInputEvent(q.mEvent, handled);
} else {
q.mEvent.recycleIfNeededAfterDispatch();
@@ -4327,7 +5436,7 @@ public final class ViewRootImpl implements ViewParent,
recycleQueuedInputEvent(q);
}
- private static boolean isTerminalInputEvent(InputEvent event) {
+ static boolean isTerminalInputEvent(InputEvent event) {
if (event instanceof KeyEvent) {
final KeyEvent keyEvent = (KeyEvent)event;
return keyEvent.getAction() == KeyEvent.ACTION_UP;
@@ -4437,7 +5546,7 @@ public final class ViewRootImpl implements ViewParent,
AttachInfo.InvalidateInfo info = mViewRects.get(i);
if (info.target == view) {
mViewRects.remove(i);
- info.release();
+ info.recycle();
}
}
@@ -4478,7 +5587,7 @@ public final class ViewRootImpl implements ViewParent,
for (int i = 0; i < viewRectCount; i++) {
final View.AttachInfo.InvalidateInfo info = mTempViewRects[i];
info.target.invalidate(info.left, info.top, info.right, info.bottom);
- info.release();
+ info.recycle();
}
}
@@ -4513,19 +5622,6 @@ public final class ViewRootImpl implements ViewParent,
public void enqueueDisplayList(DisplayList displayList) {
mDisplayLists.add(displayList);
-
- mHandler.removeMessages(MSG_INVALIDATE_DISPLAY_LIST);
- Message msg = mHandler.obtainMessage(MSG_INVALIDATE_DISPLAY_LIST);
- mHandler.sendMessage(msg);
- }
-
- public void dequeueDisplayList(DisplayList displayList) {
- if (mDisplayLists.remove(displayList)) {
- displayList.invalidate();
- if (mDisplayLists.size() == 0) {
- mHandler.removeMessages(MSG_INVALIDATE_DISPLAY_LIST);
- }
- }
}
public void cancelInvalidate(View view) {
@@ -4727,6 +5823,51 @@ public final class ViewRootImpl implements ViewParent,
postSendWindowContentChangedCallback(child);
}
+ @Override
+ public boolean canResolveLayoutDirection() {
+ return true;
+ }
+
+ @Override
+ public boolean isLayoutDirectionResolved() {
+ return true;
+ }
+
+ @Override
+ public int getLayoutDirection() {
+ return View.LAYOUT_DIRECTION_RESOLVED_DEFAULT;
+ }
+
+ @Override
+ public boolean canResolveTextDirection() {
+ return true;
+ }
+
+ @Override
+ public boolean isTextDirectionResolved() {
+ return true;
+ }
+
+ @Override
+ public int getTextDirection() {
+ return View.TEXT_DIRECTION_RESOLVED_DEFAULT;
+ }
+
+ @Override
+ public boolean canResolveTextAlignment() {
+ return true;
+ }
+
+ @Override
+ public boolean isTextAlignmentResolved() {
+ return true;
+ }
+
+ @Override
+ public int getTextAlignment() {
+ return View.TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ }
+
private View getCommonPredecessor(View first, View second) {
if (mAttachInfo != null) {
if (mTempHashSet == null) {
@@ -4831,22 +5972,6 @@ public final class ViewRootImpl implements ViewParent,
((RootViewSurfaceTaker)mView).setSurfaceKeepScreenOn(screenOn);
}
}
-
- 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);
- }
- }
- }
static class W extends IWindow.Stub {
private final WeakReference<ViewRootImpl> mViewAncestor;
@@ -4857,11 +5982,11 @@ public final class ViewRootImpl implements ViewParent,
mWindowSession = viewAncestor.mWindowSession;
}
- public void resized(Rect frame, Rect contentInsets,
+ public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
- viewAncestor.dispatchResized(frame, contentInsets,
+ viewAncestor.dispatchResized(frame, overscanInsets, contentInsets,
visibleInsets, reportDraw, newConfig);
}
}
@@ -4994,176 +6119,6 @@ public final class ViewRootImpl implements ViewParent,
}
}
- /**
- * Maintains state information for a single trackball axis, generating
- * discrete (DPAD) movements based on raw trackball motion.
- */
- static final class TrackballAxis {
- /**
- * The maximum amount of acceleration we will apply.
- */
- static final float MAX_ACCELERATION = 20;
-
- /**
- * The maximum amount of time (in milliseconds) between events in order
- * for us to consider the user to be doing fast trackball movements,
- * and thus apply an acceleration.
- */
- static final long FAST_MOVE_TIME = 150;
-
- /**
- * Scaling factor to the time (in milliseconds) between events to how
- * much to multiple/divide the current acceleration. When movement
- * is < FAST_MOVE_TIME this multiplies the acceleration; when >
- * FAST_MOVE_TIME it divides it.
- */
- static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40);
-
- float position;
- float absPosition;
- float acceleration = 1;
- long lastMoveTime = 0;
- int step;
- int dir;
- int nonAccelMovement;
-
- void reset(int _step) {
- position = 0;
- acceleration = 1;
- lastMoveTime = 0;
- step = _step;
- dir = 0;
- }
-
- /**
- * Add trackball movement into the state. If the direction of movement
- * has been reversed, the state is reset before adding the
- * movement (so that you don't have to compensate for any previously
- * collected movement before see the result of the movement in the
- * new direction).
- *
- * @return Returns the absolute value of the amount of movement
- * collected so far.
- */
- float collect(float off, long time, String axis) {
- long normTime;
- if (off > 0) {
- normTime = (long)(off * FAST_MOVE_TIME);
- if (dir < 0) {
- if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!");
- position = 0;
- step = 0;
- acceleration = 1;
- lastMoveTime = 0;
- }
- dir = 1;
- } else if (off < 0) {
- normTime = (long)((-off) * FAST_MOVE_TIME);
- if (dir > 0) {
- if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!");
- position = 0;
- step = 0;
- acceleration = 1;
- lastMoveTime = 0;
- }
- dir = -1;
- } else {
- normTime = 0;
- }
-
- // The number of milliseconds between each movement that is
- // considered "normal" and will not result in any acceleration
- // or deceleration, scaled by the offset we have here.
- if (normTime > 0) {
- long delta = time - lastMoveTime;
- lastMoveTime = time;
- float acc = acceleration;
- if (delta < normTime) {
- // The user is scrolling rapidly, so increase acceleration.
- float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR;
- if (scale > 1) acc *= scale;
- if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off="
- + off + " normTime=" + normTime + " delta=" + delta
- + " scale=" + scale + " acc=" + acc);
- acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION;
- } else {
- // The user is scrolling slowly, so decrease acceleration.
- float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR;
- if (scale > 1) acc /= scale;
- if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off="
- + off + " normTime=" + normTime + " delta=" + delta
- + " scale=" + scale + " acc=" + acc);
- acceleration = acc > 1 ? acc : 1;
- }
- }
- position += off;
- return (absPosition = Math.abs(position));
- }
-
- /**
- * Generate the number of discrete movement events appropriate for
- * the currently collected trackball movement.
- *
- * @param precision The minimum movement required to generate the
- * first discrete movement.
- *
- * @return Returns the number of discrete movements, either positive
- * or negative, or 0 if there is not enough trackball movement yet
- * for a discrete movement.
- */
- int generate(float precision) {
- int movement = 0;
- nonAccelMovement = 0;
- do {
- final int dir = position >= 0 ? 1 : -1;
- switch (step) {
- // If we are going to execute the first step, then we want
- // to do this as soon as possible instead of waiting for
- // a full movement, in order to make things look responsive.
- case 0:
- if (absPosition < precision) {
- return movement;
- }
- movement += dir;
- nonAccelMovement += dir;
- step = 1;
- break;
- // If we have generated the first movement, then we need
- // to wait for the second complete trackball motion before
- // generating the second discrete movement.
- case 1:
- if (absPosition < 2) {
- return movement;
- }
- movement += dir;
- nonAccelMovement += dir;
- position += dir > 0 ? -2 : 2;
- absPosition = Math.abs(position);
- step = 2;
- break;
- // After the first two, we generate discrete movements
- // consistently with the trackball, applying an acceleration
- // if the trackball is moving quickly. This is a simple
- // acceleration on top of what we already compute based
- // on how quickly the wheel is being turned, to apply
- // a longer increasing acceleration to continuous movement
- // in one direction.
- default:
- if (absPosition < 1) {
- return movement;
- }
- movement += dir;
- position += dir >= 0 ? -1 : 1;
- absPosition = Math.abs(position);
- float acc = acceleration;
- acc *= 1.1f;
- acceleration = acc < MAX_ACCELERATION ? acc : acceleration;
- break;
- }
- } while (true);
- }
- }
-
public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
public CalledFromWrongThreadException(String msg) {
super(msg);
@@ -5362,12 +6317,13 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid) {
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
- interactionId, callback, flags, interrogatingPid, interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid,
+ spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5399,14 +6355,16 @@ public final class ViewRootImpl implements ViewParent,
}
@Override
- public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId,
- int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid) {
+ public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
+ String viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
- .findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId,
- interactionId, callback, flags, interrogatingPid, interrogatingTid);
+ .findAccessibilityNodeInfosByViewIdClientThread(accessibilityNodeId,
+ viewId, interactionId, callback, flags, interrogatingPid,
+ interrogatingTid, spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5420,12 +6378,13 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid) {
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text,
- interactionId, callback, flags, interrogatingPid, interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid,
+ spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5439,12 +6398,12 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void findFocus(long accessibilityNodeId, int focusType, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid) {
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findFocusClientThread(accessibilityNodeId, focusType, interactionId, callback,
- flags, interrogatingPid, interrogatingTid);
+ flags, interrogatingPid, interrogatingTid, spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5458,12 +6417,12 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void focusSearch(long accessibilityNodeId, int direction, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid) {
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.focusSearchClientThread(accessibilityNodeId, direction, interactionId,
- callback, flags, interrogatingPid, interrogatingTid);
+ callback, flags, interrogatingPid, interrogatingTid, spec);
} 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 cfcf3c0..072c95f 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -33,6 +33,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
*/
public final class ViewTreeObserver {
// Recursive listeners use CopyOnWriteArrayList
+ private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners;
+ private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
@@ -49,6 +51,36 @@ public final class ViewTreeObserver {
private boolean mAlive = true;
/**
+ * Interface definition for a callback to be invoked when the view hierarchy is
+ * attached to and detached from its window.
+ */
+ public interface OnWindowAttachListener {
+ /**
+ * Callback method to be invoked when the view hierarchy is attached to a window
+ */
+ public void onWindowAttached();
+
+ /**
+ * Callback method to be invoked when the view hierarchy is detached from a window
+ */
+ public void onWindowDetached();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the view hierarchy's window
+ * focus state changes.
+ */
+ public interface OnWindowFocusChangeListener {
+ /**
+ * Callback method to be invoked when the window focus changes in the view tree.
+ *
+ * @param hasFocus Set to true if the window is gaining focus, false if it is
+ * losing focus.
+ */
+ public void onWindowFocusChanged(boolean hasFocus);
+ }
+
+ /**
* Interface definition for a callback to be invoked when the focus state within
* the view tree changes.
*/
@@ -272,6 +304,22 @@ public final class ViewTreeObserver {
* @param observer The ViewTreeObserver whose listeners must be added to this observer
*/
void merge(ViewTreeObserver observer) {
+ if (observer.mOnWindowAttachListeners != null) {
+ if (mOnWindowAttachListeners != null) {
+ mOnWindowAttachListeners.addAll(observer.mOnWindowAttachListeners);
+ } else {
+ mOnWindowAttachListeners = observer.mOnWindowAttachListeners;
+ }
+ }
+
+ if (observer.mOnWindowFocusListeners != null) {
+ if (mOnWindowFocusListeners != null) {
+ mOnWindowFocusListeners.addAll(observer.mOnWindowFocusListeners);
+ } else {
+ mOnWindowFocusListeners = observer.mOnWindowFocusListeners;
+ }
+ }
+
if (observer.mOnGlobalFocusListeners != null) {
if (mOnGlobalFocusListeners != null) {
mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
@@ -324,6 +372,76 @@ public final class ViewTreeObserver {
}
/**
+ * Register a callback to be invoked when the view hierarchy is attached to a window.
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ */
+ public void addOnWindowAttachListener(OnWindowAttachListener listener) {
+ checkIsAlive();
+
+ if (mOnWindowAttachListeners == null) {
+ mOnWindowAttachListeners
+ = new CopyOnWriteArrayList<OnWindowAttachListener>();
+ }
+
+ mOnWindowAttachListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed window attach callback.
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener)
+ */
+ public void removeOnWindowAttachListener(OnWindowAttachListener victim) {
+ checkIsAlive();
+ if (mOnWindowAttachListeners == null) {
+ return;
+ }
+ mOnWindowAttachListeners.remove(victim);
+ }
+
+ /**
+ * Register a callback to be invoked when the window focus state within the view tree changes.
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ */
+ public void addOnWindowFocusChangeListener(OnWindowFocusChangeListener listener) {
+ checkIsAlive();
+
+ if (mOnWindowFocusListeners == null) {
+ mOnWindowFocusListeners
+ = new CopyOnWriteArrayList<OnWindowFocusChangeListener>();
+ }
+
+ mOnWindowFocusListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed window focus change callback.
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener)
+ */
+ public void removeOnWindowFocusChangeListener(OnWindowFocusChangeListener victim) {
+ checkIsAlive();
+ if (mOnWindowFocusListeners == null) {
+ return;
+ }
+ mOnWindowFocusListeners.remove(victim);
+ }
+
+ /**
* Register a callback to be invoked when the focus state within the view tree changes.
*
* @param listener The callback to add
@@ -621,6 +739,41 @@ public final class ViewTreeObserver {
}
/**
+ * Notifies registered listeners that window has been attached/detached.
+ */
+ final void dispatchOnWindowAttachedChange(boolean attached) {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // 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<OnWindowAttachListener> listeners
+ = mOnWindowAttachListeners;
+ if (listeners != null && listeners.size() > 0) {
+ for (OnWindowAttachListener listener : listeners) {
+ if (attached) listener.onWindowAttached();
+ else listener.onWindowDetached();
+ }
+ }
+ }
+
+ /**
+ * Notifies registered listeners that window focus has changed.
+ */
+ final void dispatchOnWindowFocusChange(boolean hasFocus) {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // 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<OnWindowFocusChangeListener> listeners
+ = mOnWindowFocusListeners;
+ if (listeners != null && listeners.size() > 0) {
+ for (OnWindowFocusChangeListener listener : listeners) {
+ listener.onWindowFocusChanged(hasFocus);
+ }
+ }
+ }
+
+ /**
* Notifies registered listeners that focus has changed.
*/
final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) {
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index 001d020..9c00b7f 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -218,12 +218,14 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
private static class WarningDialogReceiver extends BroadcastReceiver
implements DialogInterface.OnDismissListener {
- private Context mContext;
- private Dialog mDialog;
+ private final Context mContext;
+ private final Dialog mDialog;
+ private final VolumePanel mVolumePanel;
- WarningDialogReceiver(Context context, Dialog dialog) {
+ WarningDialogReceiver(Context context, Dialog dialog, VolumePanel volumePanel) {
mContext = context;
mDialog = dialog;
+ mVolumePanel = volumePanel;
IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(this, filter);
}
@@ -231,16 +233,20 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
@Override
public void onReceive(Context context, Intent intent) {
mDialog.cancel();
- synchronized (sConfirmSafeVolumeLock) {
- sConfirmSafeVolumeDialog = null;
- }
+ cleanUp();
}
public void onDismiss(DialogInterface unused) {
mContext.unregisterReceiver(this);
+ cleanUp();
+ }
+
+ private void cleanUp() {
synchronized (sConfirmSafeVolumeLock) {
sConfirmSafeVolumeDialog = null;
}
+ mVolumePanel.forceTimeout();
+ mVolumePanel.updateStates();
}
}
@@ -276,7 +282,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
mDialog = new Dialog(context, R.style.Theme_Panel_Volume) {
public boolean onTouchEvent(MotionEvent event) {
- if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE &&
+ sConfirmSafeVolumeDialog == null) {
forceTimeout();
return true;
}
@@ -330,6 +337,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
listenToRingerMode();
}
+ public void setLayoutDirection(int layoutDirection) {
+ mPanel.setLayoutDirection(layoutDirection);
+ updateStates();
+ }
+
private void listenToRingerMode() {
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
@@ -453,8 +465,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
private void updateSlider(StreamControl sc) {
sc.seekbarView.setProgress(getStreamVolume(sc.streamType));
final boolean muted = isMuted(sc.streamType);
+ // Force reloading the image resource
+ sc.icon.setImageDrawable(null);
sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
- if (sc.streamType == AudioManager.STREAM_RING &&
+ if (((sc.streamType == AudioManager.STREAM_RING) ||
+ (sc.streamType == AudioManager.STREAM_NOTIFICATION)) &&
mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate);
}
@@ -462,7 +477,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
// never disable touch interactions for remote playback, the muting is not tied to
// the state of the phone.
sc.seekbarView.setEnabled(true);
- } else if (sc.streamType != mAudioManager.getMasterStreamType() && muted) {
+ } else if ((sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
+ (sConfirmSafeVolumeDialog != null)) {
sc.seekbarView.setEnabled(false);
} else {
sc.seekbarView.setEnabled(true);
@@ -491,7 +507,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
}
}
- private void updateStates() {
+ public void updateStates() {
final int count = mSliderGroup.getChildCount();
for (int i = 0; i < count; i++) {
StreamControl sc = (StreamControl) mSliderGroup.getChildAt(i).getTag();
@@ -563,9 +579,9 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
postMuteChanged(STREAM_MASTER, flags);
}
- public void postDisplaySafeVolumeWarning() {
+ public void postDisplaySafeVolumeWarning(int flags) {
if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return;
- obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, 0, 0).sendToTarget();
+ obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget();
}
/**
@@ -599,7 +615,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
removeMessages(MSG_FREE_RESOURCES);
sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
-
resetTimeout();
}
@@ -705,7 +720,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) ||
(streamType != mAudioManager.getMasterStreamType() &&
streamType != AudioService.STREAM_REMOTE_MUSIC &&
- isMuted(streamType))) {
+ isMuted(streamType)) ||
+ sConfirmSafeVolumeDialog != null) {
sc.seekbarView.setEnabled(false);
} else {
sc.seekbarView.setEnabled(true);
@@ -803,7 +819,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
removeMessages(MSG_FREE_RESOURCES);
sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
-
resetTimeout();
}
@@ -839,30 +854,34 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
}
}
- protected void onDisplaySafeVolumeWarning() {
- synchronized (sConfirmSafeVolumeLock) {
- if (sConfirmSafeVolumeDialog != null) {
- return;
+ protected void onDisplaySafeVolumeWarning(int flags) {
+ if ((flags & AudioManager.FLAG_SHOW_UI) != 0 || mDialog.isShowing()) {
+ 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, this);
+
+ sConfirmSafeVolumeDialog.setOnDismissListener(warning);
+ sConfirmSafeVolumeDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ sConfirmSafeVolumeDialog.show();
}
- 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();
+ updateStates();
}
+ resetTimeout();
}
/**
@@ -958,6 +977,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
mDialog.dismiss();
mActiveStreamType = -1;
}
+ synchronized (sConfirmSafeVolumeLock) {
+ if (sConfirmSafeVolumeDialog != null) {
+ sConfirmSafeVolumeDialog.dismiss();
+ }
+ }
break;
}
case MSG_RINGER_MODE_CHANGED: {
@@ -981,7 +1005,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
break;
case MSG_DISPLAY_SAFE_VOLUME_WARNING:
- onDisplaySafeVolumeWarning();
+ onDisplaySafeVolumeWarning(msg.arg1);
break;
}
}
diff --git a/core/java/android/view/WindowId.java b/core/java/android/view/WindowId.java
new file mode 100644
index 0000000..c4cda2c
--- /dev/null
+++ b/core/java/android/view/WindowId.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.IBinder;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.HashMap;
+
+/**
+ * Safe identifier for a window. This currently allows you to retrieve and observe
+ * the input focus state of the window. Most applications will
+ * not use this, instead relying on the simpler (and more efficient) methods available
+ * on {@link View}. This classes is useful when window input interactions need to be
+ * done across processes: the class itself is a Parcelable that can be passed to other
+ * processes for them to interact with your window, and it provides a limited safe API
+ * that doesn't allow the other process to negatively harm your window.
+ */
+public class WindowId implements Parcelable {
+ private final IWindowId mToken;
+
+ /**
+ * Subclass for observing changes to the focus state of an {@link WindowId}.
+ * You should use the same instance of this class for observing multiple
+ * {@link WindowId} objects, since this class is fairly heavy-weight -- the
+ * base class includes all of the mechanisms for connecting to and receiving updates
+ * from the window.
+ */
+ public static abstract class FocusObserver {
+ final IWindowFocusObserver.Stub mIObserver = new IWindowFocusObserver.Stub() {
+
+ @Override
+ public void focusGained(IBinder inputToken) {
+ WindowId token;
+ synchronized (mRegistrations) {
+ token = mRegistrations.get(inputToken);
+ }
+ if (mHandler != null) {
+ mHandler.sendMessage(mHandler.obtainMessage(1, token));
+ } else {
+ onFocusGained(token);
+ }
+ }
+
+ @Override
+ public void focusLost(IBinder inputToken) {
+ WindowId token;
+ synchronized (mRegistrations) {
+ token = mRegistrations.get(inputToken);
+ }
+ if (mHandler != null) {
+ mHandler.sendMessage(mHandler.obtainMessage(2, token));
+ } else {
+ onFocusLost(token);
+ }
+ }
+ };
+
+ final HashMap<IBinder, WindowId> mRegistrations
+ = new HashMap<IBinder, WindowId>();
+
+ class H extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case 1:
+ onFocusGained((WindowId)msg.obj);
+ break;
+ case 2:
+ onFocusLost((WindowId)msg.obj);
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+
+ final Handler mHandler;
+
+ /**
+ * Construct a new observer. This observer will be configured so that all
+ * of its callbacks are dispatched on the current calling thread.
+ */
+ public FocusObserver() {
+ mHandler = new H();
+ }
+
+ /**
+ * Called when one of the monitored windows gains input focus.
+ */
+ public abstract void onFocusGained(WindowId token);
+
+ /**
+ * Called when one of the monitored windows loses input focus.
+ */
+ public abstract void onFocusLost(WindowId token);
+ }
+
+ /**
+ * Retrieve the current focus state of the associated window.
+ */
+ public boolean isFocused() {
+ try {
+ return mToken.isFocused();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Start monitoring for changes in the focus state of the window.
+ */
+ public void registerFocusObserver(FocusObserver observer) {
+ synchronized (observer.mRegistrations) {
+ if (observer.mRegistrations.containsKey(mToken.asBinder())) {
+ throw new IllegalStateException(
+ "Focus observer already registered with input token");
+ }
+ observer.mRegistrations.put(mToken.asBinder(), this);
+ try {
+ mToken.registerFocusObserver(observer.mIObserver);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Stop monitoring changes in the focus state of the window.
+ */
+ public void unregisterFocusObserver(FocusObserver observer) {
+ synchronized (observer.mRegistrations) {
+ if (observer.mRegistrations.remove(mToken.asBinder()) == null) {
+ throw new IllegalStateException("Focus observer not registered with input token");
+ }
+ try {
+ mToken.unregisterFocusObserver(observer.mIObserver);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Comparison operator on two IntentSender objects, such that true
+ * is returned then they both represent the same operation from the
+ * same package.
+ */
+ @Override
+ public boolean equals(Object otherObj) {
+ if (otherObj instanceof WindowId) {
+ return mToken.asBinder().equals(((WindowId) otherObj)
+ .mToken.asBinder());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mToken.asBinder().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("IntentSender{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(": ");
+ sb.append(mToken != null ? mToken.asBinder() : null);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(mToken.asBinder());
+ }
+
+ public static final Parcelable.Creator<WindowId> CREATOR
+ = new Parcelable.Creator<WindowId>() {
+ public WindowId createFromParcel(Parcel in) {
+ IBinder target = in.readStrongBinder();
+ return target != null ? new WindowId(target) : null;
+ }
+
+ public WindowId[] newArray(int size) {
+ return new WindowId[size];
+ }
+ };
+
+ /** @hide */
+ public IWindowId getTarget() {
+ return mToken;
+ }
+
+ /** @hide */
+ public WindowId(IWindowId target) {
+ mToken = target;
+ }
+
+ /** @hide */
+ public WindowId(IBinder target) {
+ mToken = IWindowId.Stub.asInterface(target);
+ }
+}
diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java
deleted file mode 100644
index 7d16e14..0000000
--- a/core/java/android/view/WindowInfo.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.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 6a67d8b..541c503 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -272,7 +272,7 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
/**
- * Window type: window for showing media (e.g. video). These windows
+ * Window type: window for showing media (such as video). These windows
* are displayed behind their attached window.
*/
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1;
@@ -584,14 +584,14 @@ public interface WindowManager extends ViewManager {
/** Window flag: this window can never receive touch events. */
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
- /** Window flag: Even when this window is focusable (its
- * {@link #FLAG_NOT_FOCUSABLE is not set), allow any pointer events
+ /** Window flag: even when this window is focusable (its
+ * {@link #FLAG_NOT_FOCUSABLE} is not set), allow any pointer events
* outside of the window to be sent to the windows behind it. Otherwise
* it will consume all pointer events itself, regardless of whether they
* are inside of the window. */
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
- /** Window flag: When set, if the device is asleep when the touch
+ /** Window flag: when set, if the device is asleep when the touch
* screen is pressed, you will receive this first touch event. Usually
* the first touch event is consumed by the system since the user can
* not see what they are pressing on.
@@ -603,7 +603,7 @@ public interface WindowManager extends ViewManager {
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
/** Window flag: place the window within the entire screen, ignoring
- * decorations around the border (a.k.a. the status bar). The
+ * decorations around the border (such as the status bar). The
* window must correctly position its contents to take the screen
* decoration into account. This flag is normally set for you
* by Window as described in {@link Window#setFlags}. */
@@ -612,14 +612,30 @@ public interface WindowManager extends ViewManager {
/** Window flag: allow window to extend outside of the screen. */
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
- /** Window flag: Hide all screen decorations (e.g. status bar) while
+ /**
+ * Window flag: hide all screen decorations (such as the status bar) while
* this window is displayed. This allows the window to use the entire
* display space for itself -- the status bar will be hidden when
- * an app window with this flag set is on the top layer. */
+ * an app window with this flag set is on the top layer. A fullscreen window
+ * will ignore a value of {@link #SOFT_INPUT_ADJUST_RESIZE} for the window's
+ * {@link #softInputMode} field; the window will stay fullscreen
+ * and will not resize.
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowFullscreen} attribute; this attribute
+ * is automatically set for you in the standard fullscreen themes
+ * such as {@link android.R.style#Theme_NoTitleBar_Fullscreen},
+ * {@link android.R.style#Theme_Black_NoTitleBar_Fullscreen},
+ * {@link android.R.style#Theme_Light_NoTitleBar_Fullscreen},
+ * {@link android.R.style#Theme_Holo_NoActionBar_Fullscreen},
+ * {@link android.R.style#Theme_Holo_Light_NoActionBar_Fullscreen},
+ * {@link android.R.style#Theme_DeviceDefault_NoActionBar_Fullscreen}, and
+ * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_Fullscreen}.</p>
+ */
public static final int FLAG_FULLSCREEN = 0x00000400;
- /** Window flag: Override {@link #FLAG_FULLSCREEN and force the
- * screen decorations (such as status bar) to be shown. */
+ /** Window flag: override {@link #FLAG_FULLSCREEN} and force the
+ * screen decorations (such as the status bar) to be shown. */
public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
/** Window flag: turn on dithering when compositing this window to
@@ -628,7 +644,7 @@ public interface WindowManager extends ViewManager {
@Deprecated
public static final int FLAG_DITHER = 0x00001000;
- /** Window flag: Treat the content of the window as secure, preventing
+ /** Window flag: treat the content of the window as secure, preventing
* it from appearing in screenshots or from being viewed on non-secure
* displays.
*
@@ -697,6 +713,17 @@ public interface WindowManager extends ViewManager {
* to actually see the wallpaper behind it; this flag just ensures
* that the wallpaper surface will be there if this window actually
* has translucent regions.
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowShowWallpaper} attribute; this attribute
+ * is automatically set for you in the standard wallpaper themes
+ * such as {@link android.R.style#Theme_Wallpaper},
+ * {@link android.R.style#Theme_Wallpaper_NoTitleBar},
+ * {@link android.R.style#Theme_Wallpaper_NoTitleBar_Fullscreen},
+ * {@link android.R.style#Theme_Holo_Wallpaper},
+ * {@link android.R.style#Theme_Holo_Wallpaper_NoTitleBar},
+ * {@link android.R.style#Theme_DeviceDefault_Wallpaper}, and
+ * {@link android.R.style#Theme_DeviceDefault_Wallpaper_NoTitleBar}.</p>
*/
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
@@ -735,20 +762,20 @@ public interface WindowManager extends ViewManager {
/**
* <p>Indicates whether this window should be hardware accelerated.
* Requesting hardware acceleration does not guarantee it will happen.</p>
- *
+ *
* <p>This flag can be controlled programmatically <em>only</em> to enable
* hardware acceleration. To enable hardware acceleration for a given
* window programmatically, do the following:</p>
- *
+ *
* <pre>
* Window w = activity.getWindow(); // in Activity's onCreate() for instance
* w.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
* WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
* </pre>
- *
+ *
* <p>It is important to remember that this flag <strong>must</strong>
* be set before setting the content view of your activity or dialog.</p>
- *
+ *
* <p>This flag cannot be used to disable hardware acceleration after it
* was enabled in your manifest using
* {@link android.R.attr#hardwareAccelerated}. If you need to selectively
@@ -756,13 +783,46 @@ public interface WindowManager extends ViewManager {
* for instance), make sure it is turned off in your manifest and enable it
* on your activity or dialog when you need it instead, using the method
* described above.</p>
- *
+ *
* <p>This flag is automatically set by the system if the
* {@link android.R.attr#hardwareAccelerated android:hardwareAccelerated}
* XML attribute is set to true on an activity or on the application.</p>
*/
public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
+ /**
+ * Window flag: allow window contents to extend in to the screen's
+ * overscan area, if there is one. The window should still correctly
+ * position its contents to take the overscan area into account.
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowOverscan} attribute; this attribute
+ * is automatically set for you in the standard overscan themes
+ * such as
+ * {@link android.R.style#Theme_Holo_NoActionBar_Overscan},
+ * {@link android.R.style#Theme_Holo_Light_NoActionBar_Overscan},
+ * {@link android.R.style#Theme_DeviceDefault_NoActionBar_Overscan}, and
+ * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_Overscan}.</p>
+ *
+ * <p>When this flag is enabled for a window, its normal content may be obscured
+ * to some degree by the overscan region of the display. To ensure key parts of
+ * that content are visible to the user, you can use
+ * {@link View#setFitsSystemWindows(boolean) View.setFitsSystemWindows(boolean)}
+ * to set the point in the view hierarchy where the appropriate offsets should
+ * be applied. (This can be done either by directly calling this function, using
+ * the {@link android.R.attr#fitsSystemWindows} attribute in your view hierarchy,
+ * or implementing you own {@link View#fitSystemWindows(android.graphics.Rect)
+ * View.fitSystemWindows(Rect)} method).</p>
+ *
+ * <p>This mechanism for positioning content elements is identical to its equivalent
+ * use with layout and {@link View#setSystemUiVisibility(int)
+ * View.setSystemUiVisibility(int)}; here is an example layout that will correctly
+ * position its UI elements with this overscan flag is set:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/layout/overscan_activity.xml complete}
+ */
+ public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
+
// ----- HIDDEN FLAGS.
// These start at the high bit and go down.
@@ -953,6 +1013,12 @@ public interface WindowManager extends ViewManager {
public static final int PRIVATE_FLAG_FORCE_SHOW_NAV_BAR = 0x00000020;
/**
+ * Never animate position changes of the window.
+ *
+ * {@hide} */
+ public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040;
+
+ /**
* Control flags that are private to the platform.
* @hide
*/
@@ -1043,7 +1109,10 @@ public interface WindowManager extends ViewManager {
* method. This can <em>not</em> be combined with
* {@link #SOFT_INPUT_ADJUST_PAN}; if
* neither of these are set, then the system will try to pick one or
- * the other depending on the contents of the window.
+ * the other depending on the contents of the window. If the window's
+ * layout parameter flags include {@link #FLAG_FULLSCREEN}, this
+ * value for {@link #softInputMode} will be ignored; the window will
+ * not resize, but will stay fullscreen.
*/
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
@@ -1085,6 +1154,11 @@ public interface WindowManager extends ViewManager {
* {@link #SOFT_INPUT_ADJUST_UNSPECIFIED},
* {@link #SOFT_INPUT_ADJUST_RESIZE}, or
* {@link #SOFT_INPUT_ADJUST_PAN}.
+ * </ul>
+ *
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowSoftInputMode} attribute.</p>
*/
public int softInputMode;
@@ -1187,6 +1261,38 @@ public interface WindowManager extends ViewManager {
public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;
/**
+ * Value for {@link #rotationAnimation} to define the animation used to
+ * specify that this window will rotate in or out following a rotation.
+ */
+ public static final int ROTATION_ANIMATION_ROTATE = 0;
+
+ /**
+ * Value for {@link #rotationAnimation} to define the animation used to
+ * specify that this window will fade in or out following a rotation.
+ */
+ public static final int ROTATION_ANIMATION_CROSSFADE = 1;
+
+ /**
+ * Value for {@link #rotationAnimation} to define the animation used to
+ * specify that this window will immediately disappear or appear following
+ * a rotation.
+ */
+ public static final int ROTATION_ANIMATION_JUMPCUT = 2;
+
+ /**
+ * Define the exit and entry animations used on this window when the device is rotated.
+ * This only has an affect if the incoming and outgoing topmost
+ * opaque windows have the #FLAG_FULLSCREEN bit set and are not covered
+ * by other windows. All other situations default to the
+ * {@link #ROTATION_ANIMATION_ROTATE} behavior.
+ *
+ * @see #ROTATION_ANIMATION_ROTATE
+ * @see #ROTATION_ANIMATION_CROSSFADE
+ * @see #ROTATION_ANIMATION_JUMPCUT
+ */
+ public int rotationAnimation = ROTATION_ANIMATION_ROTATE;
+
+ /**
* Identifier for this window. This will usually be filled in for
* you.
*/
@@ -1263,7 +1369,7 @@ public interface WindowManager extends ViewManager {
/**
* Control special features of the input subsystem.
*
- * @see #INPUT_FEATURE_DISABLE_TOUCH_PAD_GESTURES
+ * @see #INPUT_FEATURE_DISABLE_POINTER_GESTURES
* @see #INPUT_FEATURE_NO_INPUT_CHANNEL
* @see #INPUT_FEATURE_DISABLE_USER_ACTIVITY
* @hide
@@ -1361,6 +1467,7 @@ public interface WindowManager extends ViewManager {
out.writeFloat(dimAmount);
out.writeFloat(screenBrightness);
out.writeFloat(buttonBrightness);
+ out.writeInt(rotationAnimation);
out.writeStrongBinder(token);
out.writeString(packageName);
TextUtils.writeToParcel(mTitle, out, parcelableFlags);
@@ -1402,6 +1509,7 @@ public interface WindowManager extends ViewManager {
dimAmount = in.readFloat();
screenBrightness = in.readFloat();
buttonBrightness = in.readFloat();
+ rotationAnimation = in.readInt();
token = in.readStrongBinder();
packageName = in.readString();
mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -1426,18 +1534,19 @@ public interface WindowManager extends ViewManager {
public static final int SOFT_INPUT_MODE_CHANGED = 1<<9;
public static final int SCREEN_ORIENTATION_CHANGED = 1<<10;
public static final int SCREEN_BRIGHTNESS_CHANGED = 1<<11;
+ public static final int ROTATION_ANIMATION_CHANGED = 1<<12;
/** {@hide} */
- public static final int BUTTON_BRIGHTNESS_CHANGED = 1<<12;
+ public static final int BUTTON_BRIGHTNESS_CHANGED = 1<<13;
/** {@hide} */
- public static final int SYSTEM_UI_VISIBILITY_CHANGED = 1<<13;
+ public static final int SYSTEM_UI_VISIBILITY_CHANGED = 1<<14;
/** {@hide} */
- public static final int SYSTEM_UI_LISTENER_CHANGED = 1<<14;
+ public static final int SYSTEM_UI_LISTENER_CHANGED = 1<<15;
/** {@hide} */
- public static final int INPUT_FEATURES_CHANGED = 1<<15;
+ public static final int INPUT_FEATURES_CHANGED = 1<<16;
/** {@hide} */
- public static final int PRIVATE_FLAGS_CHANGED = 1<<16;
+ public static final int PRIVATE_FLAGS_CHANGED = 1<<17;
/** {@hide} */
- public static final int USER_ACTIVITY_TIMEOUT_CHANGED = 1<<17;
+ public static final int USER_ACTIVITY_TIMEOUT_CHANGED = 1<<18;
/** {@hide} */
public static final int EVERYTHING_CHANGED = 0xffffffff;
@@ -1537,6 +1646,10 @@ public interface WindowManager extends ViewManager {
buttonBrightness = o.buttonBrightness;
changes |= BUTTON_BRIGHTNESS_CHANGED;
}
+ if (rotationAnimation != o.rotationAnimation) {
+ rotationAnimation = o.rotationAnimation;
+ changes |= ROTATION_ANIMATION_CHANGED;
+ }
if (screenOrientation != o.screenOrientation) {
screenOrientation = o.screenOrientation;
@@ -1639,6 +1752,10 @@ public interface WindowManager extends ViewManager {
sb.append(" bbrt=");
sb.append(buttonBrightness);
}
+ if (rotationAnimation != ROTATION_ANIMATION_ROTATE) {
+ sb.append(" rotAnim=");
+ sb.append(rotationAnimation);
+ }
if ((flags & FLAG_COMPATIBLE_WINDOW) != 0) {
sb.append(" compatible=true");
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index e8945aa..0ff46e9 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -136,11 +136,11 @@ public final class WindowManagerGlobal {
}
}
- public static IWindowSession getWindowSession(Looper mainLooper) {
+ public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
- InputMethodManager imm = InputMethodManager.getInstance(mainLooper);
+ InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
imm.getClient(), imm.getInputContext());
@@ -160,6 +160,29 @@ public final class WindowManagerGlobal {
}
}
+ public String[] getViewRootNames() {
+ synchronized (mLock) {
+ if (mRoots == null) return new String[0];
+ String[] mViewRoots = new String[mRoots.length];
+ int i = 0;
+ for (ViewRootImpl root : mRoots) {
+ mViewRoots[i++] = getWindowName(root);
+ }
+ return mViewRoots;
+ }
+ }
+
+ public View getRootView(String name) {
+ synchronized (mLock) {
+ if (mRoots == null) return null;
+ for (ViewRootImpl root : mRoots) {
+ if (name.equals(getWindowName(root))) return root.getView();
+ }
+ }
+
+ return null;
+ }
+
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
@@ -328,7 +351,7 @@ public final class WindowManagerGlobal {
View view = root.getView();
if (view != null) {
- InputMethodManager imm = InputMethodManager.getInstance(view.getContext());
+ InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews[index].getWindowToken());
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 26739b3..c0044b6 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -135,6 +135,16 @@ public interface WindowManagerPolicy {
*/
public interface WindowState {
/**
+ * Return the uid of the app that owns this window.
+ */
+ int getOwningUid();
+
+ /**
+ * Return the package name of the app that owns this window.
+ */
+ String getOwningPackage();
+
+ /**
* Perform standard frame computation. The result can be obtained with
* getFrame() if so desired. Must be called with the window manager
* lock held.
@@ -144,6 +154,8 @@ public interface WindowManagerPolicy {
* @param displayFrame The frame of the overall display in which this
* window can appear, used for constraining the overall dimensions
* of the window.
+ * @param overlayFrame The frame within the display that is inside
+ * of the overlay region.
* @param contentFrame The frame within the display in which we would
* like active content to appear. This will cause windows behind to
* be resized to match the given content frame.
@@ -155,7 +167,7 @@ public interface WindowManagerPolicy {
* are visible.
*/
public void computeFrameLw(Rect parentFrame, Rect displayFrame,
- Rect contentFrame, Rect visibleFrame);
+ Rect overlayFrame, Rect contentFrame, Rect visibleFrame);
/**
* Retrieve the current frame of the window that has been assigned by
@@ -183,6 +195,15 @@ public interface WindowManagerPolicy {
public Rect getDisplayFrameLw();
/**
+ * Retrieve the frame of the area inside the overscan region of the
+ * display that this window was last laid out in. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The rectangle holding the display overscan frame.
+ */
+ public Rect getOverscanFrameLw();
+
+ /**
* Retrieve the frame of the content area that this window was last
* laid out in. This is the area in which the content of the window
* should be placed. It will be smaller than the display frame to
@@ -401,61 +422,19 @@ public interface WindowManagerPolicy {
public void rebootSafeMode(boolean confirm);
}
- /**
- * Bit mask that is set for all enter transition.
- */
- public final int TRANSIT_ENTER_MASK = 0x1000;
-
- /**
- * Bit mask that is set for all exit transitions.
- */
- public final int TRANSIT_EXIT_MASK = 0x2000;
-
- /** Not set up for a transition. */
- public final int TRANSIT_UNSET = -1;
- /** No animation for transition. */
- public final int TRANSIT_NONE = 0;
/** Window has been added to the screen. */
- public final int TRANSIT_ENTER = 1 | TRANSIT_ENTER_MASK;
+ public static final int TRANSIT_ENTER = 1;
/** Window has been removed from the screen. */
- public final int TRANSIT_EXIT = 2 | TRANSIT_EXIT_MASK;
+ public static final int TRANSIT_EXIT = 2;
/** Window has been made visible. */
- public final int TRANSIT_SHOW = 3 | TRANSIT_ENTER_MASK;
- /** Window has been made invisible. */
- public final int TRANSIT_HIDE = 4 | TRANSIT_EXIT_MASK;
+ public static final int TRANSIT_SHOW = 3;
+ /** Window has been made invisible.
+ * TODO: Consider removal as this is unused. */
+ public static final int TRANSIT_HIDE = 4;
/** The "application starting" preview window is no longer needed, and will
* animate away to show the real window. */
- public final int TRANSIT_PREVIEW_DONE = 5;
- /** A window in a new activity is being opened on top of an existing one
- * in the same task. */
- public final int TRANSIT_ACTIVITY_OPEN = 6 | TRANSIT_ENTER_MASK;
- /** The window in the top-most activity is being closed to reveal the
- * previous activity in the same task. */
- public final int TRANSIT_ACTIVITY_CLOSE = 7 | TRANSIT_EXIT_MASK;
- /** A window in a new task is being opened on top of an existing one
- * in another activity's task. */
- public final int TRANSIT_TASK_OPEN = 8 | TRANSIT_ENTER_MASK;
- /** A window in the top-most activity is being closed to reveal the
- * previous activity in a different task. */
- public final int TRANSIT_TASK_CLOSE = 9 | TRANSIT_EXIT_MASK;
- /** A window in an existing task is being displayed on top of an existing one
- * in another activity's task. */
- public final int TRANSIT_TASK_TO_FRONT = 10 | TRANSIT_ENTER_MASK;
- /** A window in an existing task is being put below all other tasks. */
- public final int TRANSIT_TASK_TO_BACK = 11 | TRANSIT_EXIT_MASK;
- /** A window in a new activity that doesn't have a wallpaper is being
- * opened on top of one that does, effectively closing the wallpaper. */
- public final int TRANSIT_WALLPAPER_CLOSE = 12 | TRANSIT_EXIT_MASK;
- /** A window in a new activity that does have a wallpaper is being
- * opened on one that didn't, effectively opening the wallpaper. */
- public final int TRANSIT_WALLPAPER_OPEN = 13 | TRANSIT_ENTER_MASK;
- /** A window in a new activity is being opened on top of an existing one,
- * and both are on top of the wallpaper. */
- public final int TRANSIT_WALLPAPER_INTRA_OPEN = 14 | TRANSIT_ENTER_MASK;
- /** The window in the top-most activity is being closed to reveal the
- * previous activity, and both are on top of he wallpaper. */
- public final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15 | TRANSIT_EXIT_MASK;
-
+ public static final int TRANSIT_PREVIEW_DONE = 5;
+
// NOTE: screen off reasons are in order of significance, with more
// important ones lower than less important ones.
@@ -490,15 +469,23 @@ public interface WindowManagerPolicy {
public void setInitialDisplaySize(Display display, int width, int height, int density);
/**
+ * Called by window manager to set the overscan region that should be used for the
+ * given display.
+ */
+ public void setDisplayOverscan(Display display, int left, int top, int right, int bottom);
+
+ /**
* Check permissions when adding a window.
*
- * @param attrs The window's LayoutParams.
+ * @param attrs The window's LayoutParams.
+ * @param outAppOp First element will be filled with the app op corresponding to
+ * this window, or OP_NONE.
*
* @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
* else an error code, usually
* {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add.
*/
- public int checkAddPermission(WindowManager.LayoutParams attrs);
+ public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp);
/**
* Check permissions when adding a window.
@@ -708,6 +695,31 @@ public interface WindowManagerPolicy {
public int selectAnimationLw(WindowState win, int transit);
/**
+ * Determine the animation to run for a rotation transition based on the
+ * top fullscreen windows {@link WindowManager.LayoutParams#rotationAnimation}
+ * and whether it is currently fullscreen and frontmost.
+ *
+ * @param anim The exiting animation resource id is stored in anim[0], the
+ * entering animation resource id is stored in anim[1].
+ */
+ public void selectRotationAnimationLw(int anim[]);
+
+ /**
+ * Validate whether the current top fullscreen has specified the same
+ * {@link WindowManager.LayoutParams#rotationAnimation} value as that
+ * being passed in from the previous top fullscreen window.
+ *
+ * @param exitAnimId exiting resource id from the previous window.
+ * @param enterAnimId entering resource id from the previous window.
+ * @param forceDefault For rotation animations only, if true ignore the
+ * animation values and just return false.
+ * @return true if the previous values are still valid, false if they
+ * should be replaced with the default.
+ */
+ public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId,
+ boolean forceDefault);
+
+ /**
* Create and return an animation to re-display a force hidden window.
*/
public Animation createForceHideEnterAnimation(boolean onWallpaper);
@@ -933,6 +945,7 @@ public interface WindowManagerPolicy {
* @see android.app.KeyguardManager.KeyguardLock#disableKeyguard()
* @see android.app.KeyguardManager.KeyguardLock#reenableKeyguard()
*/
+ @SuppressWarnings("javadoc")
public void enableKeyguard(boolean enabled);
/**
@@ -948,6 +961,7 @@ public interface WindowManagerPolicy {
* @param callback Callback to send the result back.
* @see android.app.KeyguardManager#exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult)
*/
+ @SuppressWarnings("javadoc")
void exitKeyguardSecurely(OnKeyguardExitResult callback);
/**
@@ -1077,6 +1091,16 @@ public interface WindowManagerPolicy {
public void keepScreenOnStoppedLw();
/**
+ * Gets the current user rotation mode.
+ *
+ * @return The rotation mode.
+ *
+ * @see WindowManagerPolicy#USER_ROTATION_LOCKED
+ * @see WindowManagerPolicy#USER_ROTATION_FREE
+ */
+ public int getUserRotationMode();
+
+ /**
* Inform the policy that the user has chosen a preferred orientation ("rotation lock").
*
* @param mode One of {@link WindowManagerPolicy#USER_ROTATION_LOCKED} or
@@ -1112,14 +1136,6 @@ public interface WindowManagerPolicy {
public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
/**
- * Returns whether magnification can be applied to the given window type.
- *
- * @param attrs The window's LayoutParams.
- * @return Whether magnification can be applied.
- */
- public boolean canMagnifyWindowLw(WindowManager.LayoutParams attrs);
-
- /**
* Called when the current user changes. Guaranteed to be called before the broadcast
* of the new user id is made to all listeners.
*
@@ -1142,4 +1158,23 @@ public interface WindowManagerPolicy {
* {@link android.content.Intent#ACTION_ASSIST}
*/
public void showAssistant();
+
+ /**
+ * Returns whether a given window type can be magnified.
+ *
+ * @param windowType The window type.
+ * @return True if the window can be magnified.
+ */
+ public boolean canMagnifyWindow(int windowType);
+
+ /**
+ * Returns whether a given window type is considered a top level one.
+ * A top level window does not have a container, i.e. attached window,
+ * or if it has a container it is laid out as a top-level window, not
+ * as a child of its container.
+ *
+ * @param windowType The window type.
+ * @return True if the window is a top level one.
+ */
+ public boolean isTopLevelWindow(int windowType);
}
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
deleted file mode 100644
index bf77c67..0000000
--- a/core/java/android/view/WindowOrientationListener.java
+++ /dev/null
@@ -1,715 +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.view;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.SystemProperties;
-import android.util.FloatMath;
-import android.util.Log;
-import android.util.Slog;
-
-/**
- * A special helper class used by the WindowManager
- * for receiving notifications from the SensorManager when
- * the orientation of the device has changed.
- *
- * NOTE: If changing anything here, please run the API demo
- * "App/Activity/Screen Orientation" to ensure that all orientation
- * modes still work correctly.
- *
- * You can also visualize the behavior of the WindowOrientationListener.
- * Refer to frameworks/base/tools/orientationplot/README.txt for details.
- *
- * @hide
- */
-public abstract class WindowOrientationListener {
- private static final String TAG = "WindowOrientationListener";
- private static final boolean LOG = SystemProperties.getBoolean(
- "debug.orientation.log", false);
-
- private static final boolean USE_GRAVITY_SENSOR = false;
-
- private SensorManager mSensorManager;
- private boolean mEnabled;
- private int mRate;
- private Sensor mSensor;
- private SensorEventListenerImpl mSensorEventListener;
- int mCurrentRotation = -1;
-
- /**
- * Creates a new WindowOrientationListener.
- *
- * @param context for the WindowOrientationListener.
- */
- public WindowOrientationListener(Context context) {
- this(context, SensorManager.SENSOR_DELAY_UI);
- }
-
- /**
- * Creates a new WindowOrientationListener.
- *
- * @param context for the WindowOrientationListener.
- * @param rate at which sensor events are processed (see also
- * {@link android.hardware.SensorManager SensorManager}). Use the default
- * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
- * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
- *
- * This constructor is private since no one uses it.
- */
- private WindowOrientationListener(Context context, int rate) {
- mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
- mRate = rate;
- mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR
- ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER);
- if (mSensor != null) {
- // Create listener only if sensors do exist
- mSensorEventListener = new SensorEventListenerImpl(this);
- }
- }
-
- /**
- * Enables the WindowOrientationListener so it will monitor the sensor and call
- * {@link #onOrientationChanged} when the device orientation changes.
- */
- public void enable() {
- if (mSensor == null) {
- Log.w(TAG, "Cannot detect sensors. Not enabled");
- return;
- }
- if (mEnabled == false) {
- if (LOG) {
- Log.d(TAG, "WindowOrientationListener enabled");
- }
- mSensorEventListener.reset();
- mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
- mEnabled = true;
- }
- }
-
- /**
- * Disables the WindowOrientationListener.
- */
- public void disable() {
- if (mSensor == null) {
- Log.w(TAG, "Cannot detect sensors. Invalid disable");
- return;
- }
- if (mEnabled == true) {
- if (LOG) {
- Log.d(TAG, "WindowOrientationListener disabled");
- }
- mSensorManager.unregisterListener(mSensorEventListener);
- mEnabled = false;
- }
- }
-
- /**
- * Sets the current rotation.
- *
- * @param rotation The current rotation.
- */
- public void setCurrentRotation(int rotation) {
- mCurrentRotation = rotation;
- }
-
- /**
- * Gets the proposed rotation.
- *
- * This method only returns a rotation if the orientation listener is certain
- * of its proposal. If the rotation is indeterminate, returns -1.
- *
- * @return The proposed rotation, or -1 if unknown.
- */
- public int getProposedRotation() {
- if (mEnabled) {
- return mSensorEventListener.getProposedRotation();
- }
- return -1;
- }
-
- /**
- * Returns true if sensor is enabled and false otherwise
- */
- public boolean canDetectOrientation() {
- return mSensor != null;
- }
-
- /**
- * Called when the rotation view of the device has changed.
- *
- * This method is called whenever the orientation becomes certain of an orientation.
- * It is called each time the orientation determination transitions from being
- * uncertain to being certain again, even if it is the same orientation as before.
- *
- * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants.
- * @see Surface
- */
- public abstract void onProposedRotationChanged(int rotation);
-
- /**
- * This class filters the raw accelerometer data and tries to detect actual changes in
- * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
- * but here's the outline:
- *
- * - Low-pass filter the accelerometer vector in cartesian coordinates. We do it in
- * cartesian space because the orientation calculations are sensitive to the
- * absolute magnitude of the acceleration. In particular, there are singularities
- * in the calculation as the magnitude approaches 0. By performing the low-pass
- * filtering early, we can eliminate most spurious high-frequency impulses due to noise.
- *
- * - Convert the acceleromter vector from cartesian to spherical coordinates.
- * Since we're dealing with rotation of the device, this is the sensible coordinate
- * system to work in. The zenith direction is the Z-axis, the direction the screen
- * is facing. The radial distance is referred to as the magnitude below.
- * The elevation angle is referred to as the "tilt" below.
- * The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
- * the Y-axis).
- * See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
- *
- * - If the tilt angle is too close to horizontal (near 90 or -90 degrees), do nothing.
- * The orientation angle is not meaningful when the device is nearly horizontal.
- * The tilt angle thresholds are set differently for each orientation and different
- * limits are applied when the device is facing down as opposed to when it is facing
- * forward or facing up.
- *
- * - When the orientation angle reaches a certain threshold, consider transitioning
- * to the corresponding orientation. These thresholds have some hysteresis built-in
- * to avoid oscillations between adjacent orientations.
- *
- * - Wait for the device to settle for a little bit. Once that happens, issue the
- * new orientation proposal.
- *
- * Details are explained inline.
- *
- * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
- * signal processing background.
- */
- static final class SensorEventListenerImpl implements SensorEventListener {
- // We work with all angles in degrees in this class.
- private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
-
- // Number of nanoseconds per millisecond.
- private static final long NANOS_PER_MS = 1000000;
-
- // Indices into SensorEvent.values for the accelerometer sensor.
- private static final int ACCELEROMETER_DATA_X = 0;
- private static final int ACCELEROMETER_DATA_Y = 1;
- private static final int ACCELEROMETER_DATA_Z = 2;
-
- private final WindowOrientationListener mOrientationListener;
-
- // The minimum amount of time that a predicted rotation must be stable before it
- // is accepted as a valid rotation proposal. This value can be quite small because
- // the low-pass filter already suppresses most of the noise so we're really just
- // looking for quick confirmation that the last few samples are in agreement as to
- // the desired orientation.
- private static final long PROPOSAL_SETTLE_TIME_NANOS = 40 * NANOS_PER_MS;
-
- // The minimum amount of time that must have elapsed since the device last exited
- // the flat state (time since it was picked up) before the proposed rotation
- // can change.
- private static final long PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS = 500 * NANOS_PER_MS;
-
- // The minimum amount of time that must have elapsed since the device stopped
- // swinging (time since device appeared to be in the process of being put down
- // or put away into a pocket) before the proposed rotation can change.
- private static final long PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS = 300 * NANOS_PER_MS;
-
- // The minimum amount of time that must have elapsed since the device stopped
- // undergoing external acceleration before the proposed rotation can change.
- private static final long PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS =
- 500 * NANOS_PER_MS;
-
- // If the tilt angle remains greater than the specified angle for a minimum of
- // the specified time, then the device is deemed to be lying flat
- // (just chillin' on a table).
- private static final float FLAT_ANGLE = 75;
- private static final long FLAT_TIME_NANOS = 1000 * NANOS_PER_MS;
-
- // If the tilt angle has increased by at least delta degrees within the specified amount
- // of time, then the device is deemed to be swinging away from the user
- // down towards flat (tilt = 90).
- private static final float SWING_AWAY_ANGLE_DELTA = 20;
- private static final long SWING_TIME_NANOS = 300 * NANOS_PER_MS;
-
- // The maximum sample inter-arrival time in milliseconds.
- // If the acceleration samples are further apart than this amount in time, we reset the
- // state of the low-pass filter and orientation properties. This helps to handle
- // boundary conditions when the device is turned on, wakes from suspend or there is
- // a significant gap in samples.
- private static final long MAX_FILTER_DELTA_TIME_NANOS = 1000 * NANOS_PER_MS;
-
- // The acceleration filter time constant.
- //
- // This time constant is used to tune the acceleration filter such that
- // impulses and vibrational noise (think car dock) is suppressed before we
- // try to calculate the tilt and orientation angles.
- //
- // The filter time constant is related to the filter cutoff frequency, which is the
- // frequency at which signals are attenuated by 3dB (half the passband power).
- // Each successive octave beyond this frequency is attenuated by an additional 6dB.
- //
- // Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz
- // is given by Fc = 1 / (2pi * t).
- //
- // The higher the time constant, the lower the cutoff frequency, so more noise
- // will be suppressed.
- //
- // Filtering adds latency proportional the time constant (inversely proportional
- // to the cutoff frequency) so we don't want to make the time constant too
- // large or we can lose responsiveness. Likewise we don't want to make it too
- // small or we do a poor job suppressing acceleration spikes.
- // Empirically, 100ms seems to be too small and 500ms is too large.
- private static final float FILTER_TIME_CONSTANT_MS = 200.0f;
-
- /* State for orientation detection. */
-
- // Thresholds for minimum and maximum allowable deviation from gravity.
- //
- // If the device is undergoing external acceleration (being bumped, in a car
- // that is turning around a corner or a plane taking off) then the magnitude
- // may be substantially more or less than gravity. This can skew our orientation
- // detection by making us think that up is pointed in a different direction.
- //
- // Conversely, if the device is in freefall, then there will be no gravity to
- // measure at all. This is problematic because we cannot detect the orientation
- // without gravity to tell us which way is up. A magnitude near 0 produces
- // singularities in the tilt and orientation calculations.
- //
- // In both cases, we postpone choosing an orientation.
- //
- // However, we need to tolerate some acceleration because the angular momentum
- // of turning the device can skew the observed acceleration for a short period of time.
- private static final float NEAR_ZERO_MAGNITUDE = 1; // m/s^2
- private static final float ACCELERATION_TOLERANCE = 4; // m/s^2
- private static final float MIN_ACCELERATION_MAGNITUDE =
- SensorManager.STANDARD_GRAVITY - ACCELERATION_TOLERANCE;
- private static final float MAX_ACCELERATION_MAGNITUDE =
- SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE;
-
- // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
- // when screen is facing the sky or ground), we completely ignore orientation data.
- private static final int MAX_TILT = 75;
-
- // The tilt angle range in degrees for each orientation.
- // Beyond these tilt angles, we don't even consider transitioning into the
- // specified orientation. We place more stringent requirements on unnatural
- // orientations than natural ones to make it less likely to accidentally transition
- // into those states.
- // The first value of each pair is negative so it applies a limit when the device is
- // facing down (overhead reading in bed).
- // The second value of each pair is positive so it applies a limit when the device is
- // facing up (resting on a table).
- // The ideal tilt angle is 0 (when the device is vertical) so the limits establish
- // how close to vertical the device must be in order to change orientation.
- private static final int[][] TILT_TOLERANCE = new int[][] {
- /* ROTATION_0 */ { -25, 70 },
- /* ROTATION_90 */ { -25, 65 },
- /* ROTATION_180 */ { -25, 60 },
- /* ROTATION_270 */ { -25, 65 }
- };
-
- // The gap angle in degrees between adjacent orientation angles for hysteresis.
- // This creates a "dead zone" between the current orientation and a proposed
- // adjacent orientation. No orientation proposal is made when the orientation
- // angle is within the gap between the current orientation and the adjacent
- // orientation.
- private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 45;
-
- // Timestamp and value of the last accelerometer sample.
- private long mLastFilteredTimestampNanos;
- private float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
-
- // The last proposed rotation, -1 if unknown.
- private int mProposedRotation;
-
- // Value of the current predicted rotation, -1 if unknown.
- private int mPredictedRotation;
-
- // Timestamp of when the predicted rotation most recently changed.
- private long mPredictedRotationTimestampNanos;
-
- // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed).
- private long mFlatTimestampNanos;
-
- // Timestamp when the device last appeared to be swinging.
- private long mSwingTimestampNanos;
-
- // Timestamp when the device last appeared to be undergoing external acceleration.
- private long mAccelerationTimestampNanos;
-
- // History of observed tilt angles.
- private static final int TILT_HISTORY_SIZE = 40;
- private float[] mTiltHistory = new float[TILT_HISTORY_SIZE];
- private long[] mTiltHistoryTimestampNanos = new long[TILT_HISTORY_SIZE];
- private int mTiltHistoryIndex;
-
- public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
- mOrientationListener = orientationListener;
- reset();
- }
-
- public int getProposedRotation() {
- return mProposedRotation;
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- }
-
- @Override
- public void onSensorChanged(SensorEvent event) {
- // The vector given in the SensorEvent points straight up (towards the sky) under ideal
- // conditions (the phone is not accelerating). I'll call this up vector elsewhere.
- float x = event.values[ACCELEROMETER_DATA_X];
- float y = event.values[ACCELEROMETER_DATA_Y];
- float z = event.values[ACCELEROMETER_DATA_Z];
-
- if (LOG) {
- Slog.v(TAG, "Raw acceleration vector: "
- + "x=" + x + ", y=" + y + ", z=" + z
- + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z));
- }
-
- // Apply a low-pass filter to the acceleration up vector in cartesian space.
- // Reset the orientation listener state if the samples are too far apart in time
- // or when we see values of (0, 0, 0) which indicates that we polled the
- // accelerometer too soon after turning it on and we don't have any data yet.
- final long now = event.timestamp;
- final long then = mLastFilteredTimestampNanos;
- final float timeDeltaMS = (now - then) * 0.000001f;
- final boolean skipSample;
- if (now < then
- || now > then + MAX_FILTER_DELTA_TIME_NANOS
- || (x == 0 && y == 0 && z == 0)) {
- if (LOG) {
- Slog.v(TAG, "Resetting orientation listener.");
- }
- reset();
- skipSample = true;
- } else {
- final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS);
- x = alpha * (x - mLastFilteredX) + mLastFilteredX;
- y = alpha * (y - mLastFilteredY) + mLastFilteredY;
- z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
- if (LOG) {
- Slog.v(TAG, "Filtered acceleration vector: "
- + "x=" + x + ", y=" + y + ", z=" + z
- + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z));
- }
- skipSample = false;
- }
- mLastFilteredTimestampNanos = now;
- mLastFilteredX = x;
- mLastFilteredY = y;
- mLastFilteredZ = z;
-
- boolean isAccelerating = false;
- boolean isFlat = false;
- boolean isSwinging = false;
- if (!skipSample) {
- // Calculate the magnitude of the acceleration vector.
- final float magnitude = FloatMath.sqrt(x * x + y * y + z * z);
- if (magnitude < NEAR_ZERO_MAGNITUDE) {
- if (LOG) {
- Slog.v(TAG, "Ignoring sensor data, magnitude too close to zero.");
- }
- clearPredictedRotation();
- } else {
- // Determine whether the device appears to be undergoing external acceleration.
- if (isAccelerating(magnitude)) {
- isAccelerating = true;
- mAccelerationTimestampNanos = now;
- }
-
- // Calculate the tilt angle.
- // This is the angle between the up vector and the x-y plane (the plane of
- // the screen) in a range of [-90, 90] degrees.
- // -90 degrees: screen horizontal and facing the ground (overhead)
- // 0 degrees: screen vertical
- // 90 degrees: screen horizontal and facing the sky (on table)
- final int tiltAngle = (int) Math.round(
- Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
- addTiltHistoryEntry(now, tiltAngle);
-
- // Determine whether the device appears to be flat or swinging.
- if (isFlat(now)) {
- isFlat = true;
- mFlatTimestampNanos = now;
- }
- if (isSwinging(now, tiltAngle)) {
- isSwinging = true;
- mSwingTimestampNanos = now;
- }
-
- // If the tilt angle is too close to horizontal then we cannot determine
- // the orientation angle of the screen.
- if (Math.abs(tiltAngle) > MAX_TILT) {
- if (LOG) {
- Slog.v(TAG, "Ignoring sensor data, tilt angle too high: "
- + "tiltAngle=" + tiltAngle);
- }
- clearPredictedRotation();
- } else {
- // Calculate the orientation angle.
- // This is the angle between the x-y projection of the up vector onto
- // the +y-axis, increasing clockwise in a range of [0, 360] degrees.
- int orientationAngle = (int) Math.round(
- -Math.atan2(-x, y) * RADIANS_TO_DEGREES);
- if (orientationAngle < 0) {
- // atan2 returns [-180, 180]; normalize to [0, 360]
- orientationAngle += 360;
- }
-
- // Find the nearest rotation.
- int nearestRotation = (orientationAngle + 45) / 90;
- if (nearestRotation == 4) {
- nearestRotation = 0;
- }
-
- // Determine the predicted orientation.
- if (isTiltAngleAcceptable(nearestRotation, tiltAngle)
- && isOrientationAngleAcceptable(nearestRotation,
- orientationAngle)) {
- updatePredictedRotation(now, nearestRotation);
- if (LOG) {
- Slog.v(TAG, "Predicted: "
- + "tiltAngle=" + tiltAngle
- + ", orientationAngle=" + orientationAngle
- + ", predictedRotation=" + mPredictedRotation
- + ", predictedRotationAgeMS="
- + ((now - mPredictedRotationTimestampNanos)
- * 0.000001f));
- }
- } else {
- if (LOG) {
- Slog.v(TAG, "Ignoring sensor data, no predicted rotation: "
- + "tiltAngle=" + tiltAngle
- + ", orientationAngle=" + orientationAngle);
- }
- clearPredictedRotation();
- }
- }
- }
- }
-
- // Determine new proposed rotation.
- final int oldProposedRotation = mProposedRotation;
- if (mPredictedRotation < 0 || isPredictedRotationAcceptable(now)) {
- mProposedRotation = mPredictedRotation;
- }
-
- // Write final statistics about where we are in the orientation detection process.
- if (LOG) {
- Slog.v(TAG, "Result: currentRotation=" + mOrientationListener.mCurrentRotation
- + ", proposedRotation=" + mProposedRotation
- + ", predictedRotation=" + mPredictedRotation
- + ", timeDeltaMS=" + timeDeltaMS
- + ", isAccelerating=" + isAccelerating
- + ", isFlat=" + isFlat
- + ", isSwinging=" + isSwinging
- + ", timeUntilSettledMS=" + remainingMS(now,
- mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS)
- + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now,
- mAccelerationTimestampNanos + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS)
- + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now,
- mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS)
- + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now,
- mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS));
- }
-
- // Tell the listener.
- if (mProposedRotation != oldProposedRotation && mProposedRotation >= 0) {
- if (LOG) {
- Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + mProposedRotation
- + ", oldProposedRotation=" + oldProposedRotation);
- }
- mOrientationListener.onProposedRotationChanged(mProposedRotation);
- }
- }
-
- /**
- * Returns true if the tilt angle is acceptable for a given predicted rotation.
- */
- private boolean isTiltAngleAcceptable(int rotation, int tiltAngle) {
- return tiltAngle >= TILT_TOLERANCE[rotation][0]
- && tiltAngle <= TILT_TOLERANCE[rotation][1];
- }
-
- /**
- * Returns true if the orientation angle is acceptable for a given predicted rotation.
- *
- * This function takes into account the gap between adjacent orientations
- * for hysteresis.
- */
- private boolean isOrientationAngleAcceptable(int rotation, int orientationAngle) {
- // If there is no current rotation, then there is no gap.
- // The gap is used only to introduce hysteresis among advertised orientation
- // changes to avoid flapping.
- final int currentRotation = mOrientationListener.mCurrentRotation;
- if (currentRotation >= 0) {
- // If the specified rotation is the same or is counter-clockwise adjacent
- // to the current rotation, then we set a lower bound on the orientation angle.
- // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_90,
- // then we want to check orientationAngle > 45 + GAP / 2.
- if (rotation == currentRotation
- || rotation == (currentRotation + 1) % 4) {
- int lowerBound = rotation * 90 - 45
- + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
- if (rotation == 0) {
- if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
- return false;
- }
- } else {
- if (orientationAngle < lowerBound) {
- return false;
- }
- }
- }
-
- // If the specified rotation is the same or is clockwise adjacent,
- // then we set an upper bound on the orientation angle.
- // For example, if currentRotation is ROTATION_0 and rotation is ROTATION_270,
- // then we want to check orientationAngle < 315 - GAP / 2.
- if (rotation == currentRotation
- || rotation == (currentRotation + 3) % 4) {
- int upperBound = rotation * 90 + 45
- - ADJACENT_ORIENTATION_ANGLE_GAP / 2;
- if (rotation == 0) {
- if (orientationAngle <= 45 && orientationAngle > upperBound) {
- return false;
- }
- } else {
- if (orientationAngle > upperBound) {
- return false;
- }
- }
- }
- }
- return true;
- }
-
- /**
- * Returns true if the predicted rotation is ready to be advertised as a
- * proposed rotation.
- */
- private boolean isPredictedRotationAcceptable(long now) {
- // The predicted rotation must have settled long enough.
- if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) {
- return false;
- }
-
- // The last flat state (time since picked up) must have been sufficiently long ago.
- if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) {
- return false;
- }
-
- // The last swing state (time since last movement to put down) must have been
- // sufficiently long ago.
- if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) {
- return false;
- }
-
- // The last acceleration state must have been sufficiently long ago.
- if (now < mAccelerationTimestampNanos
- + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) {
- return false;
- }
-
- // Looks good!
- return true;
- }
-
- private void reset() {
- mLastFilteredTimestampNanos = Long.MIN_VALUE;
- mProposedRotation = -1;
- mFlatTimestampNanos = Long.MIN_VALUE;
- mSwingTimestampNanos = Long.MIN_VALUE;
- mAccelerationTimestampNanos = Long.MIN_VALUE;
- clearPredictedRotation();
- clearTiltHistory();
- }
-
- private void clearPredictedRotation() {
- mPredictedRotation = -1;
- mPredictedRotationTimestampNanos = Long.MIN_VALUE;
- }
-
- private void updatePredictedRotation(long now, int rotation) {
- if (mPredictedRotation != rotation) {
- mPredictedRotation = rotation;
- mPredictedRotationTimestampNanos = now;
- }
- }
-
- private boolean isAccelerating(float magnitude) {
- return magnitude < MIN_ACCELERATION_MAGNITUDE
- || magnitude > MAX_ACCELERATION_MAGNITUDE;
- }
-
- private void clearTiltHistory() {
- mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE;
- mTiltHistoryIndex = 1;
- }
-
- private void addTiltHistoryEntry(long now, float tilt) {
- mTiltHistory[mTiltHistoryIndex] = tilt;
- mTiltHistoryTimestampNanos[mTiltHistoryIndex] = now;
- mTiltHistoryIndex = (mTiltHistoryIndex + 1) % TILT_HISTORY_SIZE;
- mTiltHistoryTimestampNanos[mTiltHistoryIndex] = Long.MIN_VALUE;
- }
-
- private boolean isFlat(long now) {
- for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndex(i)) >= 0; ) {
- if (mTiltHistory[i] < FLAT_ANGLE) {
- break;
- }
- if (mTiltHistoryTimestampNanos[i] + FLAT_TIME_NANOS <= now) {
- // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS.
- return true;
- }
- }
- return false;
- }
-
- private boolean isSwinging(long now, float tilt) {
- for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndex(i)) >= 0; ) {
- if (mTiltHistoryTimestampNanos[i] + SWING_TIME_NANOS < now) {
- break;
- }
- if (mTiltHistory[i] + SWING_AWAY_ANGLE_DELTA <= tilt) {
- // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS.
- return true;
- }
- }
- return false;
- }
-
- private int nextTiltHistoryIndex(int index) {
- index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1;
- return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1;
- }
-
- private static float remainingMS(long now, long until) {
- return now >= until ? 0 : (until - now) * 0.000001f;
- }
- }
-}
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 1500905..dbeca1f 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -19,6 +19,7 @@ package android.view.accessibility;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.Pools.SynchronizedPool;
import java.util.ArrayList;
import java.util.List;
@@ -686,11 +687,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
private static final int MAX_POOL_SIZE = 10;
- private static final Object sPoolLock = new Object();
- private static AccessibilityEvent sPool;
- private static int sPoolSize;
- private AccessibilityEvent mNext;
- private boolean mIsInPool;
+ private static final SynchronizedPool<AccessibilityEvent> sPool =
+ new SynchronizedPool<AccessibilityEvent>(MAX_POOL_SIZE);
private int mEventType;
private CharSequence mPackageName;
@@ -916,17 +914,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* @return An instance.
*/
public static AccessibilityEvent obtain() {
- synchronized (sPoolLock) {
- if (sPool != null) {
- AccessibilityEvent event = sPool;
- sPool = sPool.mNext;
- sPoolSize--;
- event.mNext = null;
- event.mIsInPool = false;
- return event;
- }
- return new AccessibilityEvent();
- }
+ AccessibilityEvent event = sPool.acquire();
+ return (event != null) ? event : new AccessibilityEvent();
}
/**
@@ -939,18 +928,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
*/
@Override
public void recycle() {
- if (mIsInPool) {
- throw new IllegalStateException("Event already recycled!");
- }
clear();
- synchronized (sPoolLock) {
- if (sPoolSize <= MAX_POOL_SIZE) {
- mNext = sPool;
- sPool = this;
- mIsInPool = true;
- sPoolSize++;
- }
- }
+ sPool.release(this);
}
/**
@@ -1137,54 +1116,176 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* @return The string representation.
*/
public static String eventTypeToString(int eventType) {
- switch (eventType) {
- case TYPE_VIEW_CLICKED:
- return "TYPE_VIEW_CLICKED";
- case TYPE_VIEW_LONG_CLICKED:
- return "TYPE_VIEW_LONG_CLICKED";
- case TYPE_VIEW_SELECTED:
- return "TYPE_VIEW_SELECTED";
- case TYPE_VIEW_FOCUSED:
- return "TYPE_VIEW_FOCUSED";
- case TYPE_VIEW_TEXT_CHANGED:
- return "TYPE_VIEW_TEXT_CHANGED";
- case TYPE_WINDOW_STATE_CHANGED:
- return "TYPE_WINDOW_STATE_CHANGED";
- case TYPE_VIEW_HOVER_ENTER:
- return "TYPE_VIEW_HOVER_ENTER";
- case TYPE_VIEW_HOVER_EXIT:
- return "TYPE_VIEW_HOVER_EXIT";
- case TYPE_NOTIFICATION_STATE_CHANGED:
- return "TYPE_NOTIFICATION_STATE_CHANGED";
- case TYPE_TOUCH_EXPLORATION_GESTURE_START:
- return "TYPE_TOUCH_EXPLORATION_GESTURE_START";
- case TYPE_TOUCH_EXPLORATION_GESTURE_END:
- return "TYPE_TOUCH_EXPLORATION_GESTURE_END";
- case TYPE_WINDOW_CONTENT_CHANGED:
- return "TYPE_WINDOW_CONTENT_CHANGED";
- case TYPE_VIEW_TEXT_SELECTION_CHANGED:
- return "TYPE_VIEW_TEXT_SELECTION_CHANGED";
- case TYPE_VIEW_SCROLLED:
- return "TYPE_VIEW_SCROLLED";
- case TYPE_ANNOUNCEMENT:
- return "TYPE_ANNOUNCEMENT";
- case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
- return "TYPE_VIEW_ACCESSIBILITY_FOCUSED";
- case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
- 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;
+ if (eventType == TYPES_ALL_MASK) {
+ return "TYPES_ALL_MASK";
}
+ StringBuilder builder = new StringBuilder();
+ int eventTypeCount = 0;
+ while (eventType != 0) {
+ final int eventTypeFlag = 1 << Integer.numberOfTrailingZeros(eventType);
+ eventType &= ~eventTypeFlag;
+ switch (eventTypeFlag) {
+ case TYPE_VIEW_CLICKED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_CLICKED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_LONG_CLICKED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_LONG_CLICKED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_SELECTED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_SELECTED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_FOCUSED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_FOCUSED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_TEXT_CHANGED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_TEXT_CHANGED");
+ eventTypeCount++;
+ } break;
+ case TYPE_WINDOW_STATE_CHANGED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_WINDOW_STATE_CHANGED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_HOVER_ENTER: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_HOVER_ENTER");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_HOVER_EXIT: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_HOVER_EXIT");
+ eventTypeCount++;
+ } break;
+ case TYPE_NOTIFICATION_STATE_CHANGED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_NOTIFICATION_STATE_CHANGED");
+ eventTypeCount++;
+ } break;
+ case TYPE_TOUCH_EXPLORATION_GESTURE_START: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_TOUCH_EXPLORATION_GESTURE_START");
+ eventTypeCount++;
+ } break;
+ case TYPE_TOUCH_EXPLORATION_GESTURE_END: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_TOUCH_EXPLORATION_GESTURE_END");
+ eventTypeCount++;
+ } break;
+ case TYPE_WINDOW_CONTENT_CHANGED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_WINDOW_CONTENT_CHANGED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_TEXT_SELECTION_CHANGED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_SCROLLED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_SCROLLED");
+ eventTypeCount++;
+ } break;
+ case TYPE_ANNOUNCEMENT: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_ANNOUNCEMENT");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_ACCESSIBILITY_FOCUSED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY");
+ eventTypeCount++;
+ } break;
+ case TYPE_GESTURE_DETECTION_START: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_GESTURE_DETECTION_START");
+ eventTypeCount++;
+ } break;
+ case TYPE_GESTURE_DETECTION_END: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_GESTURE_DETECTION_END");
+ eventTypeCount++;
+ } break;
+ case TYPE_TOUCH_INTERACTION_START: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_TOUCH_INTERACTION_START");
+ eventTypeCount++;
+ } break;
+ case TYPE_TOUCH_INTERACTION_END: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_TOUCH_INTERACTION_END");
+ eventTypeCount++;
+ } break;
+ }
+ }
+ if (eventTypeCount > 1) {
+ builder.insert(0, '[');
+ builder.append(']');
+ }
+ return builder.toString();
}
/**
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 20b5f17..84d7e72 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -17,7 +17,6 @@
package android.view.accessibility;
import android.accessibilityservice.IAccessibilityServiceConnection;
-import android.graphics.Rect;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -102,8 +101,6 @@ public final class AccessibilityInteractionClient
private Message mSameThreadMessage;
- private final Rect mTempBounds = new Rect();
-
// The connection cache is shared between all interrogating threads.
private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
new SparseArray<IAccessibilityServiceConnection>();
@@ -194,14 +191,14 @@ public final class AccessibilityInteractionClient
return cachedInfo;
}
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
+ final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
accessibilityWindowId, accessibilityNodeId, interactionId, this,
prefetchFlags, Thread.currentThread().getId());
// If the scale is zero the call has failed.
- if (windowScale > 0) {
+ if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, windowScale);
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
if (infos != null && !infos.isEmpty()) {
return infos.get(0);
}
@@ -233,25 +230,25 @@ public final class AccessibilityInteractionClient
* where to start the search. Use
* {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
- * @param viewId The id of the view.
- * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+ * @param viewId The fully qualified resource name of the view id to find.
+ * @return An list of {@link AccessibilityNodeInfo} if found, empty list otherwise.
*/
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int connectionId,
- int accessibilityWindowId, long accessibilityNodeId, int viewId) {
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(int connectionId,
+ int accessibilityWindowId, long accessibilityNodeId, String viewId) {
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final float windowScale =
- connection.findAccessibilityNodeInfoByViewId(accessibilityWindowId,
- accessibilityNodeId, viewId, interactionId, this,
- Thread.currentThread().getId());
- // If the scale is zero the call has failed.
- if (windowScale > 0) {
- AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+ final boolean success = connection.findAccessibilityNodeInfosByViewId(
+ accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
+ Thread.currentThread().getId());
+ if (success) {
+ List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
- return info;
+ if (infos != null) {
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ return infos;
+ }
}
} else {
if (DEBUG) {
@@ -264,7 +261,7 @@ public final class AccessibilityInteractionClient
+ " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
}
}
- return null;
+ return Collections.emptyList();
}
/**
@@ -290,15 +287,16 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final float windowScale = connection.findAccessibilityNodeInfosByText(
+ final boolean success = connection.findAccessibilityNodeInfosByText(
accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
Thread.currentThread().getId());
- // If the scale is zero the call has failed.
- if (windowScale > 0) {
+ if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, windowScale);
- return infos;
+ if (infos != null) {
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ return infos;
+ }
}
} else {
if (DEBUG) {
@@ -336,14 +334,13 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final float windowScale = connection.findFocus(accessibilityWindowId,
+ final boolean success = connection.findFocus(accessibilityWindowId,
accessibilityNodeId, focusType, interactionId, this,
Thread.currentThread().getId());
- // If the scale is zero the call has failed.
- if (windowScale > 0) {
+ if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
return info;
}
} else {
@@ -381,14 +378,13 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final float windowScale = connection.focusSearch(accessibilityWindowId,
+ final boolean success = connection.focusSearch(accessibilityWindowId,
accessibilityNodeId, direction, interactionId, this,
Thread.currentThread().getId());
- // If the scale is zero the call has failed.
- if (windowScale > 0) {
+ if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
return info;
}
} else {
@@ -604,36 +600,14 @@ public final class AccessibilityInteractionClient
}
/**
- * Applies compatibility scale to the info bounds if it is not equal to one.
- *
- * @param info The info whose bounds to scale.
- * @param scale The scale to apply.
- */
- private void applyCompatibilityScaleIfNeeded(AccessibilityNodeInfo info, float scale) {
- if (scale == 1.0f) {
- return;
- }
- Rect bounds = mTempBounds;
- info.getBoundsInParent(bounds);
- bounds.scale(scale);
- info.setBoundsInParent(bounds);
-
- info.getBoundsInScreen(bounds);
- bounds.scale(scale);
- info.setBoundsInScreen(bounds);
- }
-
- /**
* Finalize an {@link AccessibilityNodeInfo} before passing it to the client.
*
* @param info The info.
* @param connectionId The id of the connection to the system.
- * @param windowScale The source window compatibility scale.
*/
- private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId,
- float windowScale) {
+ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info,
+ int connectionId) {
if (info != null) {
- applyCompatibilityScaleIfNeeded(info, windowScale);
info.setConnectionId(connectionId);
info.setSealed(true);
sAccessibilityNodeInfoCache.add(info);
@@ -645,15 +619,14 @@ public final class AccessibilityInteractionClient
*
* @param infos The {@link AccessibilityNodeInfo}s.
* @param connectionId The id of the connection to the system.
- * @param windowScale The source window compatibility scale.
*/
private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
- int connectionId, float windowScale) {
+ int connectionId) {
if (infos != null) {
final int infosCount = infos.size();
for (int i = 0; i < infosCount; i++) {
AccessibilityNodeInfo info = infos.get(i);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
}
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 1dc2487..d9c9b69 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -16,10 +16,12 @@
package android.view.accessibility;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Pools.SynchronizedPool;
import android.util.SparseLongArray;
import android.view.View;
@@ -77,7 +79,10 @@ public class AccessibilityNodeInfo implements Parcelable {
public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004;
/** @hide */
- public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+ public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+
+ /** @hide */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
// Actions.
@@ -126,16 +131,22 @@ public class AccessibilityNodeInfo implements Parcelable {
* at a given movement granularity. For example, move to the next character,
* word, etc.
* <p>
- * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<br>
- * <strong>Example:</strong>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
+ * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the previous character and do not extend selection.
* <code><pre><p>
* Bundle arguments = new Bundle();
* arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
* AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
* info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
* </code></pre></p>
* </p>
*
+ * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
* @see #setMovementGranularities(int)
* @see #getMovementGranularities()
*
@@ -152,17 +163,23 @@ public class AccessibilityNodeInfo implements Parcelable {
* at a given movement granularity. For example, move to the next character,
* word, etc.
* <p>
- * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<br>
- * <strong>Example:</strong>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
+ * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the next character and do not extend selection.
* <code><pre><p>
* Bundle arguments = new Bundle();
* arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
* AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
* info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
* arguments);
* </code></pre></p>
* </p>
*
+ * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
* @see #setMovementGranularities(int)
* @see #getMovementGranularities()
*
@@ -215,15 +232,53 @@ public class AccessibilityNodeInfo implements Parcelable {
public static final int ACTION_SCROLL_BACKWARD = 0x00002000;
/**
+ * Action to copy the current selection to the clipboard.
+ */
+ public static final int ACTION_COPY = 0x00004000;
+
+ /**
+ * Action to paste the current clipboard content.
+ */
+ public static final int ACTION_PASTE = 0x00008000;
+
+ /**
+ * Action to cut the current selection and place it to the clipboard.
+ */
+ public static final int ACTION_CUT = 0x00010000;
+
+ /**
+ * Action to set the selection. Performing this action with no arguments
+ * clears the selection.
+ * <p>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_SELECTION_START_INT},
+ * {@link #ACTION_ARGUMENT_SELECTION_END_INT}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1);
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2);
+ * info.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see #ACTION_ARGUMENT_SELECTION_START_INT
+ * @see #ACTION_ARGUMENT_SELECTION_END_INT
+ */
+ public static final int ACTION_SET_SELECTION = 0x00020000;
+
+ /**
* Argument for which movement granularity to be used when traversing the node text.
* <p>
* <strong>Type:</strong> int<br>
* <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY},
* {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}
* </p>
+ *
+ * @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ * @see #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
*/
public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT =
- "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
+ "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
/**
* Argument for which HTML element to get moving to the next/previous HTML element.
@@ -232,9 +287,51 @@ public class AccessibilityNodeInfo implements Parcelable {
* <strong>Actions:</strong> {@link #ACTION_NEXT_HTML_ELEMENT},
* {@link #ACTION_PREVIOUS_HTML_ELEMENT}
* </p>
+ *
+ * @see #ACTION_NEXT_HTML_ELEMENT
+ * @see #ACTION_PREVIOUS_HTML_ELEMENT
*/
public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING =
- "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
+ "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
+
+ /**
+ * Argument for whether when moving at granularity to extend the selection
+ * or to move it otherwise.
+ * <p>
+ * <strong>Type:</strong> boolean<br>
+ * <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY},
+ * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}
+ * </p>
+ *
+ * @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ * @see #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ */
+ public static final String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN =
+ "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";
+
+ /**
+ * Argument for specifying the selection start.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong> {@link #ACTION_SET_SELECTION}
+ * </p>
+ *
+ * @see #ACTION_SET_SELECTION
+ */
+ public static final String ACTION_ARGUMENT_SELECTION_START_INT =
+ "ACTION_ARGUMENT_SELECTION_START_INT";
+
+ /**
+ * Argument for specifying the selection end.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong> {@link #ACTION_SET_SELECTION}
+ * </p>
+ *
+ * @see #ACTION_SET_SELECTION
+ */
+ public static final String ACTION_ARGUMENT_SELECTION_END_INT =
+ "ACTION_ARGUMENT_SELECTION_END_INT";
/**
* The input focus.
@@ -275,29 +372,31 @@ public class AccessibilityNodeInfo implements Parcelable {
// Boolean attributes.
- private static final int PROPERTY_CHECKABLE = 0x00000001;
+ private static final int BOOLEAN_PROPERTY_CHECKABLE = 0x00000001;
+
+ private static final int BOOLEAN_PROPERTY_CHECKED = 0x00000002;
- private static final int PROPERTY_CHECKED = 0x00000002;
+ private static final int BOOLEAN_PROPERTY_FOCUSABLE = 0x00000004;
- private static final int PROPERTY_FOCUSABLE = 0x00000004;
+ private static final int BOOLEAN_PROPERTY_FOCUSED = 0x00000008;
- private static final int PROPERTY_FOCUSED = 0x00000008;
+ private static final int BOOLEAN_PROPERTY_SELECTED = 0x00000010;
- private static final int PROPERTY_SELECTED = 0x00000010;
+ private static final int BOOLEAN_PROPERTY_CLICKABLE = 0x00000020;
- private static final int PROPERTY_CLICKABLE = 0x00000020;
+ private static final int BOOLEAN_PROPERTY_LONG_CLICKABLE = 0x00000040;
- private static final int PROPERTY_LONG_CLICKABLE = 0x00000040;
+ private static final int BOOLEAN_PROPERTY_ENABLED = 0x00000080;
- private static final int PROPERTY_ENABLED = 0x00000080;
+ private static final int BOOLEAN_PROPERTY_PASSWORD = 0x00000100;
- private static final int PROPERTY_PASSWORD = 0x00000100;
+ private static final int BOOLEAN_PROPERTY_SCROLLABLE = 0x00000200;
- private static final int PROPERTY_SCROLLABLE = 0x00000200;
+ private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
- private static final int PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
+ private static final int BOOLEAN_PROPERTY_VISIBLE_TO_USER = 0x00000800;
- private static final int PROPERTY_VISIBLE_TO_USER = 0x00000800;
+ private static final int BOOLEAN_PROPERTY_EDITABLE = 0x00001000;
/**
* Bits that provide the id of a virtual descendant of a view.
@@ -354,11 +453,9 @@ public class AccessibilityNodeInfo implements Parcelable {
// Housekeeping.
private static final int MAX_POOL_SIZE = 50;
- private static final Object sPoolLock = new Object();
- private static AccessibilityNodeInfo sPool;
- private static int sPoolSize;
- private AccessibilityNodeInfo mNext;
- private boolean mIsInPool;
+ private static final SynchronizedPool<AccessibilityNodeInfo> sPool =
+ new SynchronizedPool<AccessibilityNodeInfo>(MAX_POOL_SIZE);
+
private boolean mSealed;
// Data.
@@ -376,12 +473,16 @@ public class AccessibilityNodeInfo implements Parcelable {
private CharSequence mClassName;
private CharSequence mText;
private CharSequence mContentDescription;
+ private String mViewIdResourceName;
private final SparseLongArray mChildNodeIds = new SparseLongArray();
private int mActions;
private int mMovementGranularities;
+ private int mTextSelectionStart = UNDEFINED;
+ private int mTextSelectionEnd = UNDEFINED;
+
private int mConnectionId = UNDEFINED;
/**
@@ -487,6 +588,31 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Refreshes this info with the latest state of the view it represents.
+ * <p>
+ * <strong>Note:</strong> If this method returns false this info is obsolete
+ * since it represents a view that is no longer in the view tree and should
+ * be recycled.
+ * </p>
+ * @return Whether the refresh succeeded.
+ */
+ public boolean refresh() {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return false;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ AccessibilityNodeInfo refreshedInfo = client.findAccessibilityNodeInfoByAccessibilityId(
+ mConnectionId, mWindowId, mSourceNodeId, 0);
+ if (refreshedInfo == null) {
+ return false;
+ }
+ init(refreshedInfo);
+ refreshedInfo.recycle();
+ return true;
+ }
+
+ /**
* @return The ids of the children.
*
* @hide
@@ -705,6 +831,37 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Finds {@link AccessibilityNodeInfo}s by the fully qualified view id's resource
+ * name where a fully qualified id is of the from "package:id/id_resource_name".
+ * For example, if the target application's package is "foo.bar" and the id
+ * resource name is "baz", the fully qualified resource id is "foo.bar:id/baz".
+ *
+ * <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>
+ * <p>
+ * <strong>Note:</strong> The primary usage of this API is for UI test automation
+ * and in order to report the fully qualified view id if an {@link AccessibilityNodeInfo}
+ * the client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+ * flag when configuring his {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+ *
+ * @param viewId The fully qualified resource name of the view id to find.
+ * @return A list of node info.
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String viewId) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return Collections.emptyList();
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfosByViewId(mConnectionId, mWindowId, mSourceNodeId,
+ viewId);
+ }
+
+ /**
* Gets the parent.
* <p>
* <strong>Note:</strong> It is a client responsibility to recycle the
@@ -835,7 +992,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is checkable.
*/
public boolean isCheckable() {
- return getBooleanProperty(PROPERTY_CHECKABLE);
+ return getBooleanProperty(BOOLEAN_PROPERTY_CHECKABLE);
}
/**
@@ -851,7 +1008,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setCheckable(boolean checkable) {
- setBooleanProperty(PROPERTY_CHECKABLE, checkable);
+ setBooleanProperty(BOOLEAN_PROPERTY_CHECKABLE, checkable);
}
/**
@@ -860,7 +1017,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is checked.
*/
public boolean isChecked() {
- return getBooleanProperty(PROPERTY_CHECKED);
+ return getBooleanProperty(BOOLEAN_PROPERTY_CHECKED);
}
/**
@@ -876,7 +1033,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setChecked(boolean checked) {
- setBooleanProperty(PROPERTY_CHECKED, checked);
+ setBooleanProperty(BOOLEAN_PROPERTY_CHECKED, checked);
}
/**
@@ -885,7 +1042,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is focusable.
*/
public boolean isFocusable() {
- return getBooleanProperty(PROPERTY_FOCUSABLE);
+ return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE);
}
/**
@@ -901,7 +1058,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setFocusable(boolean focusable) {
- setBooleanProperty(PROPERTY_FOCUSABLE, focusable);
+ setBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE, focusable);
}
/**
@@ -910,7 +1067,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is focused.
*/
public boolean isFocused() {
- return getBooleanProperty(PROPERTY_FOCUSED);
+ return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED);
}
/**
@@ -926,7 +1083,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setFocused(boolean focused) {
- setBooleanProperty(PROPERTY_FOCUSED, focused);
+ setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused);
}
/**
@@ -935,7 +1092,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return Whether the node is visible to the user.
*/
public boolean isVisibleToUser() {
- return getBooleanProperty(PROPERTY_VISIBLE_TO_USER);
+ return getBooleanProperty(BOOLEAN_PROPERTY_VISIBLE_TO_USER);
}
/**
@@ -951,7 +1108,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setVisibleToUser(boolean visibleToUser) {
- setBooleanProperty(PROPERTY_VISIBLE_TO_USER, visibleToUser);
+ setBooleanProperty(BOOLEAN_PROPERTY_VISIBLE_TO_USER, visibleToUser);
}
/**
@@ -960,7 +1117,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is accessibility focused.
*/
public boolean isAccessibilityFocused() {
- return getBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED);
+ return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED);
}
/**
@@ -976,7 +1133,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setAccessibilityFocused(boolean focused) {
- setBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED, focused);
+ setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED, focused);
}
/**
@@ -985,7 +1142,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is selected.
*/
public boolean isSelected() {
- return getBooleanProperty(PROPERTY_SELECTED);
+ return getBooleanProperty(BOOLEAN_PROPERTY_SELECTED);
}
/**
@@ -1001,7 +1158,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setSelected(boolean selected) {
- setBooleanProperty(PROPERTY_SELECTED, selected);
+ setBooleanProperty(BOOLEAN_PROPERTY_SELECTED, selected);
}
/**
@@ -1010,7 +1167,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is clickable.
*/
public boolean isClickable() {
- return getBooleanProperty(PROPERTY_CLICKABLE);
+ return getBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE);
}
/**
@@ -1026,7 +1183,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setClickable(boolean clickable) {
- setBooleanProperty(PROPERTY_CLICKABLE, clickable);
+ setBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE, clickable);
}
/**
@@ -1035,7 +1192,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is long clickable.
*/
public boolean isLongClickable() {
- return getBooleanProperty(PROPERTY_LONG_CLICKABLE);
+ return getBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE);
}
/**
@@ -1051,7 +1208,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setLongClickable(boolean longClickable) {
- setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable);
+ setBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE, longClickable);
}
/**
@@ -1060,7 +1217,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is enabled.
*/
public boolean isEnabled() {
- return getBooleanProperty(PROPERTY_ENABLED);
+ return getBooleanProperty(BOOLEAN_PROPERTY_ENABLED);
}
/**
@@ -1076,7 +1233,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setEnabled(boolean enabled) {
- setBooleanProperty(PROPERTY_ENABLED, enabled);
+ setBooleanProperty(BOOLEAN_PROPERTY_ENABLED, enabled);
}
/**
@@ -1085,7 +1242,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is a password.
*/
public boolean isPassword() {
- return getBooleanProperty(PROPERTY_PASSWORD);
+ return getBooleanProperty(BOOLEAN_PROPERTY_PASSWORD);
}
/**
@@ -1101,7 +1258,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setPassword(boolean password) {
- setBooleanProperty(PROPERTY_PASSWORD, password);
+ setBooleanProperty(BOOLEAN_PROPERTY_PASSWORD, password);
}
/**
@@ -1110,7 +1267,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is scrollable, false otherwise.
*/
public boolean isScrollable() {
- return getBooleanProperty(PROPERTY_SCROLLABLE);
+ return getBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE);
}
/**
@@ -1127,7 +1284,32 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public void setScrollable(boolean scrollable) {
enforceNotSealed();
- setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
+ setBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE, scrollable);
+ }
+
+ /**
+ * Gets if the node is editable.
+ *
+ * @return True if the node is editable, false otherwise.
+ */
+ public boolean isEditable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_EDITABLE);
+ }
+
+ /**
+ * Sets whether this node is editable.
+ * <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 editable True if the node is editable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setEditable(boolean editable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_EDITABLE, editable);
}
/**
@@ -1349,6 +1531,75 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Sets the fully qualified resource name of the source view's id.
+ *
+ * <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 viewIdResName The id resource name.
+ */
+ public void setViewIdResourceName(String viewIdResName) {
+ enforceNotSealed();
+ mViewIdResourceName = viewIdResName;
+ }
+
+ /**
+ * Gets the fully qualified resource name of the source view's id.
+ *
+ * <p>
+ * <strong>Note:</strong> The primary usage of this API is for UI test automation
+ * and in order to report the source view id of an {@link AccessibilityNodeInfo} the
+ * client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+ * flag when configuring his {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+
+ * @return The id resource name.
+ */
+ public String getViewIdResourceName() {
+ return mViewIdResourceName;
+ }
+
+ /**
+ * Gets the text selection start.
+ *
+ * @return The text selection start if there is selection or -1.
+ */
+ public int getTextSelectionStart() {
+ return mTextSelectionStart;
+ }
+
+ /**
+ * Gets the text selection end.
+ *
+ * @return The text selection end if there is selection or -1.
+ */
+ public int getTextSelectionEnd() {
+ return mTextSelectionEnd;
+ }
+
+ /**
+ * Sets the text selection start and end.
+ * <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 start The text selection start.
+ * @param end The text selection end.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setTextSelection(int start, int end) {
+ enforceNotSealed();
+ mTextSelectionStart = start;
+ mTextSelectionEnd = end;
+ }
+
+ /**
* Gets the value of a boolean property.
*
* @param property The property.
@@ -1517,17 +1768,8 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return An instance.
*/
public static AccessibilityNodeInfo obtain() {
- synchronized (sPoolLock) {
- if (sPool != null) {
- AccessibilityNodeInfo info = sPool;
- sPool = sPool.mNext;
- sPoolSize--;
- info.mNext = null;
- info.mIsInPool = false;
- return info;
- }
- return new AccessibilityNodeInfo();
- }
+ AccessibilityNodeInfo info = sPool.acquire();
+ return (info != null) ? info : new AccessibilityNodeInfo();
}
/**
@@ -1552,18 +1794,8 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If the info is already recycled.
*/
public void recycle() {
- if (mIsInPool) {
- throw new IllegalStateException("Info already recycled!");
- }
clear();
- synchronized (sPoolLock) {
- if (sPoolSize <= MAX_POOL_SIZE) {
- mNext = sPool;
- sPool = this;
- mIsInPool = true;
- sPoolSize++;
- }
- }
+ sPool.release(this);
}
/**
@@ -1609,6 +1841,10 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeCharSequence(mClassName);
parcel.writeCharSequence(mText);
parcel.writeCharSequence(mContentDescription);
+ parcel.writeString(mViewIdResourceName);
+
+ parcel.writeInt(mTextSelectionStart);
+ parcel.writeInt(mTextSelectionEnd);
// Since instances of this class are fetched via synchronous i.e. blocking
// calls in IPCs we always recycle as soon as the instance is marshaled.
@@ -1620,7 +1856,6 @@ public class AccessibilityNodeInfo implements Parcelable {
*
* @param other The other instance.
*/
- @SuppressWarnings("unchecked")
private void init(AccessibilityNodeInfo other) {
mSealed = other.mSealed;
mSourceNodeId = other.mSourceNodeId;
@@ -1635,6 +1870,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mClassName = other.mClassName;
mText = other.mText;
mContentDescription = other.mContentDescription;
+ mViewIdResourceName = other.mViewIdResourceName;
mActions= other.mActions;
mBooleanProperties = other.mBooleanProperties;
mMovementGranularities = other.mMovementGranularities;
@@ -1642,6 +1878,8 @@ public class AccessibilityNodeInfo implements Parcelable {
for (int i = 0; i < otherChildIdCount; i++) {
mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i));
}
+ mTextSelectionStart = other.mTextSelectionStart;
+ mTextSelectionEnd = other.mTextSelectionEnd;
}
/**
@@ -1685,6 +1923,10 @@ public class AccessibilityNodeInfo implements Parcelable {
mClassName = parcel.readCharSequence();
mText = parcel.readCharSequence();
mContentDescription = parcel.readCharSequence();
+ mViewIdResourceName = parcel.readString();
+
+ mTextSelectionStart = parcel.readInt();
+ mTextSelectionEnd = parcel.readInt();
}
/**
@@ -1707,7 +1949,10 @@ public class AccessibilityNodeInfo implements Parcelable {
mClassName = null;
mText = null;
mContentDescription = null;
+ mViewIdResourceName = null;
mActions = 0;
+ mTextSelectionStart = UNDEFINED;
+ mTextSelectionEnd = UNDEFINED;
}
/**
@@ -1746,8 +1991,16 @@ public class AccessibilityNodeInfo implements Parcelable {
return "ACTION_SCROLL_FORWARD";
case ACTION_SCROLL_BACKWARD:
return "ACTION_SCROLL_BACKWARD";
+ case ACTION_CUT:
+ return "ACTION_CUT";
+ case ACTION_COPY:
+ return "ACTION_COPY";
+ case ACTION_PASTE:
+ return "ACTION_PASTE";
+ case ACTION_SET_SELECTION:
+ return "ACTION_SET_SELECTION";
default:
- throw new IllegalArgumentException("Unknown action: " + action);
+ return"ACTION_UNKNOWN";
}
}
@@ -1851,6 +2104,7 @@ public class AccessibilityNodeInfo implements Parcelable {
builder.append("; className: ").append(mClassName);
builder.append("; text: ").append(mText);
builder.append("; contentDescription: ").append(mContentDescription);
+ builder.append("; viewIdResName: ").append(mViewIdResourceName);
builder.append("; checkable: ").append(isCheckable());
builder.append("; checked: ").append(isChecked());
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
index 14954be..28518aa 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
@@ -83,6 +83,7 @@ public class AccessibilityNodeInfoCache {
} break;
case AccessibilityEvent.TYPE_VIEW_FOCUSED:
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
case AccessibilityEvent.TYPE_VIEW_SELECTED:
case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index 9b39300..8d15472 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -17,6 +17,7 @@
package android.view.accessibility;
import android.os.Bundle;
+import android.view.MagnificationSpec;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
@@ -30,23 +31,23 @@ oneway interface IAccessibilityInteractionConnection {
void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid);
+ long interrogatingTid, in MagnificationSpec spec);
- void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid);
+ void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, String viewId,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, in MagnificationSpec spec);
void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid);
+ long interrogatingTid, in MagnificationSpec spec);
void findFocus(long accessibilityNodeId, int focusType, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid);
+ long interrogatingTid, in MagnificationSpec spec);
void focusSearch(long accessibilityNodeId, int direction, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid);
+ long interrogatingTid, in MagnificationSpec spec);
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 c3ef54c..fe3e5c6 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -50,7 +50,7 @@ interface IAccessibilityManager {
void removeAccessibilityInteractionConnection(IWindow windowToken);
- void registerUiTestAutomationService(IAccessibilityServiceClient client,
+ void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient client,
in AccessibilityServiceInfo info);
void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 7ec5398..d6b973e 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -42,7 +42,8 @@ class ComposingText implements NoCopySpan {
* Base class for implementors of the InputConnection interface, taking care
* of most of the common behavior for providing a connection to an Editable.
* Implementors of this class will want to be sure to implement
- * {@link #getEditable} to provide access to their own editable object.
+ * {@link #getEditable} to provide access to their own editable object, and
+ * to refer to the documentation in {@link InputConnection}.
*/
public class BaseInputConnection implements InputConnection {
private static final boolean DEBUG = false;
diff --git a/core/java/android/view/inputmethod/CompletionInfo.java b/core/java/android/view/inputmethod/CompletionInfo.java
index 3591cb1..70b8059 100644
--- a/core/java/android/view/inputmethod/CompletionInfo.java
+++ b/core/java/android/view/inputmethod/CompletionInfo.java
@@ -1,12 +1,12 @@
/*
* Copyright (C) 2007-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
@@ -23,6 +23,30 @@ import android.text.TextUtils;
/**
* Information about a single text completion that an editor has reported to
* an input method.
+ *
+ * <p>This class encapsulates a completion offered by an application
+ * that wants it to be presented to the user by the IME. Usually, apps
+ * present their completions directly in a scrolling list for example
+ * (UI developers will usually use or extend
+ * {@see android.widget.AutoCompleteTextView} to implement this).
+ * However, in some cases, the editor may not be visible, as in the
+ * case in extract mode where the IME has taken over the full
+ * screen. In this case, the editor can choose to send their
+ * completions to the IME for display.
+ *
+ * <p>Most applications who want to send completions to an IME should use
+ * {@link android.widget.AutoCompleteTextView} as this class makes this
+ * process easy. In this case, the application would not have to deal directly
+ * with this class.
+ *
+ * <p>An application who implements its own editor and wants direct control
+ * over this would create an array of CompletionInfo objects, and send it to the IME using
+ * {@link InputMethodManager#displayCompletions(View, CompletionInfo[])}.
+ * The IME would present the completions however they see fit, and
+ * call back to the editor through
+ * {@link InputConnection#commitCompletion(CompletionInfo)}.
+ * The application can then pick up the commit event by overriding
+ * {@link android.widget.TextView#onCommitCompletion(CompletionInfo)}.
*/
public final class CompletionInfo implements Parcelable {
private final long mId;
@@ -32,6 +56,12 @@ public final class CompletionInfo implements Parcelable {
/**
* Create a simple completion with just text, no label.
+ *
+ * @param id An id that get passed as is (to the editor's discretion)
+ * @param index An index that get passed as is. Typically this is the
+ * index in the list of completions inside the editor.
+ * @param text The text that should be inserted into the editor when
+ * this completion is chosen.
*/
public CompletionInfo(long id, int index, CharSequence text) {
mId = id;
@@ -41,7 +71,18 @@ public final class CompletionInfo implements Parcelable {
}
/**
- * Create a full completion with both text and label.
+ * Create a full completion with both text and label. The text is
+ * what will get inserted into the editor, while the label is what
+ * the IME should display. If they are the same, use the version
+ * of the constructor without a `label' argument.
+ *
+ * @param id An id that get passed as is (to the editor's discretion)
+ * @param index An index that get passed as is. Typically this is the
+ * index in the list of completions inside the editor.
+ * @param text The text that should be inserted into the editor when
+ * this completion is chosen.
+ * @param label The text that the IME should be showing among the
+ * completions list.
*/
public CompletionInfo(long id, int index, CharSequence text, CharSequence label) {
mId = id;
@@ -56,7 +97,7 @@ public final class CompletionInfo implements Parcelable {
mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
}
-
+
/**
* Return the abstract identifier for this completion, typically
* corresponding to the id associated with it in the original adapter.
@@ -64,7 +105,7 @@ public final class CompletionInfo implements Parcelable {
public long getId() {
return mId;
}
-
+
/**
* Return the original position of this completion, typically
* corresponding to its position in the original adapter.
@@ -72,7 +113,7 @@ public final class CompletionInfo implements Parcelable {
public int getPosition() {
return mPosition;
}
-
+
/**
* Return the actual text associated with this completion. This is the
* real text that will be inserted into the editor if the user selects it.
@@ -80,7 +121,7 @@ public final class CompletionInfo implements Parcelable {
public CharSequence getText() {
return mText;
}
-
+
/**
* Return the user-visible label for the completion, or null if the plain
* text should be shown. If non-null, this will be what the user sees as
@@ -89,7 +130,7 @@ public final class CompletionInfo implements Parcelable {
public CharSequence getLabel() {
return mLabel;
}
-
+
@Override
public String toString() {
return "CompletionInfo{#" + mPosition + " \"" + mText
diff --git a/core/java/android/view/inputmethod/ExtractedText.java b/core/java/android/view/inputmethod/ExtractedText.java
index 3b2508c..0c5d9e9 100644
--- a/core/java/android/view/inputmethod/ExtractedText.java
+++ b/core/java/android/view/inputmethod/ExtractedText.java
@@ -22,6 +22,9 @@ import android.text.TextUtils;
/**
* Information about text that has been extracted for use by an input method.
+ *
+ * This contains information about a portion of the currently edited text,
+ * that the IME should display into its own interface while in extracted mode.
*/
public class ExtractedText implements Parcelable {
/**
@@ -33,7 +36,7 @@ public class ExtractedText implements Parcelable {
* The offset in the overall text at which the extracted text starts.
*/
public int startOffset;
-
+
/**
* If the content is a report of a partial text change, this is the
* offset where the change starts and it runs until
@@ -41,7 +44,7 @@ public class ExtractedText implements Parcelable {
* field is -1.
*/
public int partialStartOffset;
-
+
/**
* If the content is a report of a partial text change, this is the offset
* where the change ends. Note that the actual text may be larger or
@@ -49,40 +52,43 @@ public class ExtractedText implements Parcelable {
* meaning a reduction or increase, respectively, in the total text.
*/
public int partialEndOffset;
-
+
/**
* The offset where the selection currently starts within the extracted
* text. The real selection start position is at
* <var>startOffset</var>+<var>selectionStart</var>.
*/
public int selectionStart;
-
+
/**
* The offset where the selection currently ends within the extracted
* text. The real selection end position is at
* <var>startOffset</var>+<var>selectionEnd</var>.
*/
public int selectionEnd;
-
+
/**
* Bit for {@link #flags}: set if the text being edited can only be on
* a single line.
*/
public static final int FLAG_SINGLE_LINE = 0x0001;
-
+
/**
* Bit for {@link #flags}: set if the editor is currently in selection mode.
+ *
+ * This happens when a hardware keyboard with latched keys is attached and
+ * the shift key is currently latched.
*/
public static final int FLAG_SELECTING = 0x0002;
-
+
/**
* Additional bit flags of information about the edited text.
*/
public int flags;
-
+
/**
* Used to package this object into a {@link Parcel}.
- *
+ *
* @param dest The {@link Parcel} to be written.
* @param flags The flags used for parceling.
*/
@@ -99,7 +105,8 @@ public class ExtractedText implements Parcelable {
/**
* Used to make this class parcelable.
*/
- public static final Parcelable.Creator<ExtractedText> CREATOR = new Parcelable.Creator<ExtractedText>() {
+ public static final Parcelable.Creator<ExtractedText> CREATOR
+ = new Parcelable.Creator<ExtractedText>() {
public ExtractedText createFromParcel(Parcel source) {
ExtractedText res = new ExtractedText();
res.text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 76c6d19..e7d84c2 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -1,12 +1,12 @@
/*
* Copyright (C) 2007-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
@@ -22,60 +22,167 @@ import android.view.KeyEvent;
/**
* The InputConnection interface is the communication channel from an
- * {@link InputMethod} back to the application that is receiving its input. It
- * is used to perform such things as reading text around the cursor,
- * committing text to the text box, and sending raw key events to the application.
- *
- * <p>Applications should never directly implement this interface, but instead
- * subclass from {@link BaseInputConnection}. This will ensure that the
- * application does not break when new methods are added to the interface.
+ * {@link InputMethod} back to the application that is receiving its
+ * input. It is used to perform such things as reading text around the
+ * cursor, committing text to the text box, and sending raw key events
+ * to the application.
+ *
+ * <p>Applications should never directly implement this interface, but
+ * instead subclass from {@link BaseInputConnection}. This will ensure
+ * that the application does not break when new methods are added to
+ * the interface.</p>
+ *
+ * <h3>Implementing an IME or an editor</h3>
+ * <p>Text input is the result of the synergy of two essential components:
+ * an Input Method Engine (IME) and an editor. The IME can be a
+ * software keyboard, a handwriting interface, an emoji palette, a
+ * speech-to-text engine, and so on. There are typically several IMEs
+ * installed on any given Android device. In Android, IMEs extend
+ * {@link android.inputmethodservice.InputMethodService}.
+ * For more information about how to create an IME, see the
+ * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
+ * Creating an input method</a> guide.
+ *
+ * The editor is the component that receives text and displays it.
+ * Typically, this is an {@link android.widget.EditText} instance, but
+ * some applications may choose to implement their own editor for
+ * various reasons. This is a large and complicated task, and an
+ * application that does this needs to make sure the behavior is
+ * consistent with standard EditText behavior in Android. An editor
+ * needs to interact with the IME, receiving commands through
+ * this InputConnection interface, and sending commands through
+ * {@link android.view.inputmethod.InputMethodManager}. An editor
+ * should start by implementing
+ * {@link android.view.View#onCreateInputConnection(EditorInfo)}
+ * to return its own input connection.</p>
+ *
+ * <p>If you are implementing your own IME, you will need to call the
+ * methods in this interface to interact with the application. Be sure
+ * to test your IME with a wide range of applications, including
+ * browsers and rich text editors, as some may have peculiarities you
+ * need to deal with. Remember your IME may not be the only source of
+ * changes on the text, and try to be as conservative as possible in
+ * the data you send and as liberal as possible in the data you
+ * receive.</p>
+ *
+ * <p>If you are implementing your own editor, you will probably need
+ * to provide your own subclass of {@link BaseInputConnection} to
+ * answer to the commands from IMEs. Please be sure to test your
+ * editor with as many IMEs as you can as their behavior can vary a
+ * lot. Also be sure to test with various languages, including CJK
+ * languages and right-to-left languages like Arabic, as these may
+ * have different input requirements. When in doubt about the
+ * behavior you should adopt for a particular call, please mimic the
+ * default TextView implementation in the latest Android version, and
+ * if you decide to drift from it, please consider carefully that
+ * inconsistencies in text edition behavior is almost universally felt
+ * as a bad thing by users.</p>
+ *
+ * <h3>Cursors, selections and compositions</h3>
+ * <p>In Android, the cursor and the selection are one and the same
+ * thing. A "cursor" is just the special case of a zero-sized
+ * selection. As such, this documentation uses them
+ * interchangeably. Any method acting "before the cursor" would act
+ * before the start of the selection if there is one, and any method
+ * acting "after the cursor" would act after the end of the
+ * selection.</p>
+ *
+ * <p>An editor needs to be able to keep track of a currently
+ * "composing" region, like the standard edition widgets do. The
+ * composition is marked in a specific style: see
+ * {@link android.text.Spanned#SPAN_COMPOSING}. IMEs use this to help
+ * the user keep track of what part of the text they are currently
+ * focusing on, and interact with the editor using
+ * {@link InputConnection#setComposingText(CharSequence, int)},
+ * {@link InputConnection#setComposingRegion(int, int)} and
+ * {@link InputConnection#finishComposingText()}.
+ * The composing region and the selection are completely independent
+ * of each other, and the IME may use them however they see fit.</p>
*/
public interface InputConnection {
/**
* Flag for use with {@link #getTextAfterCursor} and
- * {@link #getTextBeforeCursor} to have style information returned along
- * with the text. If not set, you will receive only the raw text. If
- * set, you may receive a complex CharSequence of both text and style
- * spans.
+ * {@link #getTextBeforeCursor} to have style information returned
+ * along with the text. If not set, {@link #getTextAfterCursor}
+ * sends only the raw text, without style or other spans. If set,
+ * it may return a complex CharSequence of both text and style
+ * spans. <strong>Editor authors</strong>: you should strive to
+ * send text with styles if possible, but it is not required.
*/
static final int GET_TEXT_WITH_STYLES = 0x0001;
-
+
/**
- * Flag for use with {@link #getExtractedText} to indicate you would
- * like to receive updates when the extracted text changes.
+ * Flag for use with {@link #getExtractedText} to indicate you
+ * would like to receive updates when the extracted text changes.
*/
public static final int GET_EXTRACTED_TEXT_MONITOR = 0x0001;
-
+
/**
- * Get <var>n</var> characters of text before the current cursor position.
- *
- * <p>This method may fail either if the input connection has become invalid
- * (such as its process crashing) or the client is taking too long to
- * respond with the text (it is given a couple seconds to return).
- * In either case, a null is returned.
- *
+ * Get <var>n</var> characters of text before the current cursor
+ * position.
+ *
+ * <p>This method may fail either if the input connection has
+ * become invalid (such as its process crashing) or the editor is
+ * taking too long to respond with the text (it is given a couple
+ * seconds to return). In either case, null is returned. This
+ * method does not affect the text in the editor in any way, nor
+ * does it affect the selection or composing spans.</p>
+ *
+ * <p>If {@link #GET_TEXT_WITH_STYLES} is supplied as flags, the
+ * editor should return a {@link android.text.SpannableString}
+ * with all the spans set on the text.</p>
+ *
+ * <p><strong>IME authors:</strong> please consider this will
+ * trigger an IPC round-trip that will take some time. Assume this
+ * method consumes a lot of time. Also, please keep in mind the
+ * Editor may choose to return less characters than requested even
+ * if they are available for performance reasons.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can make a change
+ * to the text and use this method right away; you need to make
+ * sure the returned value is consistent with the result of the
+ * latest edits.
+ *
* @param n The expected length of the text.
* @param flags Supplies additional options controlling how the text is
- * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
- *
- * @return Returns the text before the cursor position; the length of the
+ * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
+ * @return the text before the cursor position; the length of the
* returned text might be less than <var>n</var>.
*/
public CharSequence getTextBeforeCursor(int n, int flags);
/**
- * Get <var>n</var> characters of text after the current cursor position.
- *
- * <p>This method may fail either if the input connection has become invalid
- * (such as its process crashing) or the client is taking too long to
- * respond with the text (it is given a couple seconds to return).
- * In either case, a null is returned.
- *
+ * Get <var>n</var> characters of text after the current cursor
+ * position.
+ *
+ * <p>This method may fail either if the input connection has
+ * become invalid (such as its process crashing) or the client is
+ * taking too long to respond with the text (it is given a couple
+ * seconds to return). In either case, null is returned.
+ *
+ * <p>This method does not affect the text in the editor in any
+ * way, nor does it affect the selection or composing spans.</p>
+ *
+ * <p>If {@link #GET_TEXT_WITH_STYLES} is supplied as flags, the
+ * editor should return a {@link android.text.SpannableString}
+ * with all the spans set on the text.</p>
+ *
+ * <p><strong>IME authors:</strong> please consider this will
+ * trigger an IPC round-trip that will take some time. Assume this
+ * method consumes a lot of time.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can make a change
+ * to the text and use this method right away; you need to make
+ * sure the returned value is consistent with the result of the
+ * latest edits.</p>
+ *
* @param n The expected length of the text.
* @param flags Supplies additional options controlling how the text is
- * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
- *
- * @return Returns the text after the cursor position; the length of the
+ * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
+ *
+ * @return the text after the cursor position; the length of the
* returned text might be less than <var>n</var>.
*/
public CharSequence getTextAfterCursor(int n, int flags);
@@ -83,139 +190,287 @@ public interface InputConnection {
/**
* Gets the selected text, if any.
*
- * <p>This method may fail if either the input connection has become
- * invalid (such as its process crashing) or the client is taking too
- * long to respond with the text (it is given a couple of seconds to return).
- * In either case, a null is returned.
+ * <p>This method may fail if either the input connection has
+ * become invalid (such as its process crashing) or the client is
+ * taking too long to respond with the text (it is given a couple
+ * of seconds to return). In either case, null is returned.</p>
+ *
+ * <p>This method must not cause any changes in the editor's
+ * state.</p>
+ *
+ * <p>If {@link #GET_TEXT_WITH_STYLES} is supplied as flags, the
+ * editor should return a {@link android.text.SpannableString}
+ * with all the spans set on the text.</p>
+ *
+ * <p><strong>IME authors:</strong> please consider this will
+ * trigger an IPC round-trip that will take some time. Assume this
+ * method consumes a lot of time.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can make a change
+ * to the text or change the selection position and use this
+ * method right away; you need to make sure the returned value is
+ * consistent with the results of the latest edits.</p>
*
* @param flags Supplies additional options controlling how the text is
- * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
- * @return Returns the text that is currently selected, if any, or null if
+ * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
+ * @return the text that is currently selected, if any, or null if
* no text is selected.
*/
public CharSequence getSelectedText(int flags);
/**
- * Retrieve the current capitalization mode in effect at the current
- * cursor position in the text. See
- * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode} for
- * more information.
- *
- * <p>This method may fail either if the input connection has become invalid
- * (such as its process crashing) or the client is taking too long to
- * respond with the text (it is given a couple seconds to return).
- * In either case, a 0 is returned.
- *
+ * Retrieve the current capitalization mode in effect at the
+ * current cursor position in the text. See
+ * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}
+ * for more information.
+ *
+ * <p>This method may fail either if the input connection has
+ * become invalid (such as its process crashing) or the client is
+ * taking too long to respond with the text (it is given a couple
+ * seconds to return). In either case, 0 is returned.</p>
+ *
+ * <p>This method does not affect the text in the editor in any
+ * way, nor does it affect the selection or composing spans.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can change the
+ * cursor position and use this method right away; you need to make
+ * sure the returned value is consistent with the results of the
+ * latest edits and changes to the cursor position.</p>
+ *
* @param reqModes The desired modes to retrieve, as defined by
- * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}. These
+ * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}. These
* constants are defined so that you can simply pass the current
* {@link EditorInfo#inputType TextBoxAttribute.contentType} value
* directly in to here.
- *
- * @return Returns the caps mode flags that are in effect.
+ * @return the caps mode flags that are in effect at the current
+ * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}.
*/
public int getCursorCapsMode(int reqModes);
-
+
/**
- * Retrieve the current text in the input connection's editor, and monitor
- * for any changes to it. This function returns with the current text,
- * and optionally the input connection can send updates to the
- * input method when its text changes.
- *
- * <p>This method may fail either if the input connection has become invalid
- * (such as its process crashing) or the client is taking too long to
- * respond with the text (it is given a couple seconds to return).
- * In either case, a null is returned.
- *
+ * Retrieve the current text in the input connection's editor, and
+ * monitor for any changes to it. This function returns with the
+ * current text, and optionally the input connection can send
+ * updates to the input method when its text changes.
+ *
+ * <p>This method may fail either if the input connection has
+ * become invalid (such as its process crashing) or the client is
+ * taking too long to respond with the text (it is given a couple
+ * seconds to return). In either case, null is returned.</p>
+ *
+ * <p>Editor authors: as a general rule, try to comply with the
+ * fields in <code>request</code> for how many chars to return,
+ * but if performance or convenience dictates otherwise, please
+ * feel free to do what is most appropriate for your case. Also,
+ * if the
+ * {@link #GET_EXTRACTED_TEXT_MONITOR} flag is set, you should be
+ * calling
+ * {@link InputMethodManager#updateExtractedText(View, int, ExtractedText)}
+ * whenever you call
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)}.</p>
+ *
* @param request Description of how the text should be returned.
+ * {@link android.view.inputmethod.ExtractedTextRequest}
* @param flags Additional options to control the client, either 0 or
* {@link #GET_EXTRACTED_TEXT_MONITOR}.
- *
- * @return Returns an ExtractedText object describing the state of the
- * text view and containing the extracted text itself.
+
+ * @return an {@link android.view.inputmethod.ExtractedText}
+ * object describing the state of the text view and containing the
+ * extracted text itself, or null if the input connection is no
+ * longer valid of the editor can't comply with the request for
+ * some reason.
*/
public ExtractedText getExtractedText(ExtractedTextRequest request,
int flags);
/**
- * Delete <var>beforeLength</var> characters of text before the current cursor
- * position, and delete <var>afterLength</var> characters of text after the
- * current cursor position, excluding composing text. Before and after refer
- * to the order of the characters in the string, not to their visual representation.
- *
+ * Delete <var>beforeLength</var> characters of text before the
+ * current cursor position, and delete <var>afterLength</var>
+ * characters of text after the current cursor position, excluding
+ * the selection. Before and after refer to the order of the
+ * characters in the string, not to their visual representation:
+ * this means you don't have to figure out the direction of the
+ * text and can just use the indices as-is.
+ *
+ * <p>The lengths are supplied in Java chars, not in code points
+ * or in glyphs.</p>
+ *
+ * <p>Since this method only operates on text before and after the
+ * selection, it can't affect the contents of the selection. This
+ * may affect the composing span if the span includes characters
+ * that are to be deleted, but otherwise will not change it. If
+ * some characters in the composing span are deleted, the
+ * composing span will persist but get shortened by however many
+ * chars inside it have been removed.</p>
+ *
+ * <p><strong>IME authors:</strong> please be careful not to
+ * delete only half of a surrogate pair. Also take care not to
+ * delete more characters than are in the editor, as that may have
+ * ill effects on the application. Calling this method will cause
+ * the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * on your service after the batch input is over.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful of race
+ * conditions in implementing this call. An IME can make a change
+ * to the text or change the selection position and use this
+ * method right away; you need to make sure the effects are
+ * consistent with the results of the latest edits. Also, although
+ * the IME should not send lengths bigger than the contents of the
+ * string, you should check the values for overflows and trim the
+ * indices to the size of the contents to avoid crashes. Since
+ * this changes the contents of the editor, you need to make the
+ * changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
*
* @param beforeLength The number of characters to be deleted before the
* current cursor position.
* @param afterLength The number of characters to be deleted after the
* current cursor position.
- *
- * @return Returns true on success, false if the input connection is no longer
+ * @return true on success, false if the input connection is no longer
* valid.
*/
public boolean deleteSurroundingText(int beforeLength, int afterLength);
/**
- * Set composing text around the current cursor position with the given text,
- * and set the new cursor position. Any composing text set previously will
- * be removed automatically.
- *
+ * Set composing text around the current cursor position with the
+ * given text, and set the new cursor position. Any composing text
+ * set previously will be removed automatically.
+ *
+ * <p>If there is any composing span currently active, all
+ * characters that it comprises are removed. The passed text is
+ * added in its place, and a composing span is added to this
+ * text. Finally, the cursor is moved to the location specified by
+ * <code>newCursorPosition</code>.</p>
+ *
+ * <p>This is usually called by IMEs to add or remove or change
+ * characters in the composing span. Calling this method will
+ * cause the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * on the current IME after the batch input is over.</p>
+ *
+ * <p><strong>Editor authors:</strong> please keep in mind the
+ * text may be very similar or completely different than what was
+ * in the composing span at call time, or there may not be a
+ * composing span at all. Please note that although it's not
+ * typical use, the string may be empty. Treat this normally,
+ * replacing the currently composing text with an empty string.
+ * Also, be careful with the cursor position. IMEs rely on this
+ * working exactly as described above. Since this changes the
+ * contents of the editor, you need to make the changes known to
+ * the input method by calling
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress. Note that this method can set the cursor position
+ * on either edge of the composing text or entirely outside it,
+ * but the IME may also go on to move the cursor position to
+ * within the composing text in a subsequent call so you should
+ * make no assumption at all: the composing text and the selection
+ * are entirely independent.</p>
+ *
* @param text The composing text with styles if necessary. If no style
* object attached to the text, the default style for composing text
- * is used. See {#link android.text.Spanned} for how to attach style
- * object to the text. {#link android.text.SpannableString} and
- * {#link android.text.SpannableStringBuilder} are two
- * implementations of the interface {#link android.text.Spanned}.
- * @param newCursorPosition The new cursor position around the text. If
+ * is used. See {@link android.text.Spanned} for how to attach style
+ * object to the text. {@link android.text.SpannableString} and
+ * {@link android.text.SpannableStringBuilder} are two
+ * implementations of the interface {@link android.text.Spanned}.
+ * @param newCursorPosition The new cursor position around the text. If
* > 0, this is relative to the end of the text - 1; if <= 0, this
- * is relative to the start of the text. So a value of 1 will
+ * is relative to the start of the text. So a value of 1 will
* always advance you to the position after the full text being
- * inserted. Note that this means you can't position the cursor
+ * inserted. Note that this means you can't position the cursor
* within the text, because the editor can make modifications to
* the text you are providing so it is not possible to correctly
* specify locations there.
- *
- * @return Returns true on success, false if the input connection is no longer
+ * @return true on success, false if the input connection is no longer
* valid.
*/
public boolean setComposingText(CharSequence text, int newCursorPosition);
/**
- * Mark a certain region of text as composing text. Any composing text set
- * previously will be removed automatically. The default style for composing
- * text is used.
+ * Mark a certain region of text as composing text. If there was a
+ * composing region, the characters are left as they were and the
+ * composing span removed, as if {@link #finishComposingText()}
+ * has been called. The default style for composing text is used.
+ *
+ * <p>The passed indices are clipped to the contents bounds. If
+ * the resulting region is zero-sized, no region is marked and the
+ * effect is the same as that of calling {@link #finishComposingText()}.
+ * The order of start and end is not important. In effect, the
+ * region from start to end and the region from end to start is
+ * the same. Editor authors, be ready to accept a start that is
+ * greater than end.</p>
+ *
+ * <p>Since this does not change the contents of the text, editors should not call
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)} and
+ * IMEs should not receive
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}.
+ * </p>
+ *
+ * <p>This has no impact on the cursor/selection position. It may
+ * result in the cursor being anywhere inside or outside the
+ * composing region, including cases where the selection and the
+ * composing region overlap partially or entirely.</p>
*
* @param start the position in the text at which the composing region begins
* @param end the position in the text at which the composing region ends
- * @return Returns true on success, false if the input connection is no longer
+ * @return true on success, false if the input connection is no longer
* valid.
*/
public boolean setComposingRegion(int start, int end);
/**
- * Have the text editor finish whatever composing text is currently
- * active. This simply leaves the text as-is, removing any special
- * composing styling or other state that was around it. The cursor
- * position remains unchanged.
+ * Have the text editor finish whatever composing text is
+ * currently active. This simply leaves the text as-is, removing
+ * any special composing styling or other state that was around
+ * it. The cursor position remains unchanged.
+ *
+ * <p><strong>IME authors:</strong> be aware that this call may be
+ * expensive with some editors.</p>
+ *
+ * <p><strong>Editor authors:</strong> please note that the cursor
+ * may be anywhere in the contents when this is called, including
+ * in the middle of the composing span or in a completely
+ * unrelated place. It must not move.</p>
+ *
+ * @return true on success, false if the input connection
+ * is no longer valid.
*/
public boolean finishComposingText();
-
+
/**
* Commit text to the text box and set the new cursor position.
- * Any composing text set previously will be removed
- * automatically.
- *
- * @param text The committed text.
- * @param newCursorPosition The new cursor position around the text. If
+ *
+ * <p>This method removes the contents of the currently composing
+ * text and replaces it with the passed CharSequence, and then
+ * moves the cursor according to {@code newCursorPosition}.
+ * This behaves like calling
+ * {@link #setComposingText(CharSequence, int) setComposingText(text, newCursorPosition)}
+ * then {@link #finishComposingText()}.</p>
+ *
+ * <p>Calling this method will cause the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * on the current IME after the batch input is over.
+ * <strong>Editor authors</strong>, for this to happen you need to
+ * make the changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * @param text The committed text. This may include styles.
+ * @param newCursorPosition The new cursor position around the text. If
* > 0, this is relative to the end of the text - 1; if <= 0, this
- * is relative to the start of the text. So a value of 1 will
+ * is relative to the start of the text. So a value of 1 will
* always advance you to the position after the full text being
- * inserted. Note that this means you can't position the cursor
+ * inserted. Note that this means you can't position the cursor
* within the text, because the editor can make modifications to
* the text you are providing so it is not possible to correctly
* specify locations there.
- *
- *
- * @return Returns true on success, false if the input connection is no longer
+ * @return true on success, false if the input connection is no longer
* valid.
*/
public boolean commitText(CharSequence text, int newCursorPosition);
@@ -223,49 +478,102 @@ public interface InputConnection {
/**
* Commit a completion the user has selected from the possible ones
* previously reported to {@link InputMethodSession#displayCompletions
- * InputMethodSession.displayCompletions()}. This will result in the
- * same behavior as if the user had selected the completion from the
- * actual UI.
- *
+ * InputMethodSession#displayCompletions(CompletionInfo[])} or
+ * {@link InputMethodManager#displayCompletions
+ * InputMethodManager#displayCompletions(View, CompletionInfo[])}.
+ * This will result in the same behavior as if the user had
+ * selected the completion from the actual UI. In all other
+ * respects, this behaves like {@link #commitText(CharSequence, int)}.
+ *
+ * <p><strong>IME authors:</strong> please take care to send the
+ * same object that you received through
+ * {@link android.inputmethodservice.InputMethodService#onDisplayCompletions(CompletionInfo[])}.
+ * </p>
+ *
+ * <p><strong>Editor authors:</strong> if you never call
+ * {@link InputMethodSession#displayCompletions(CompletionInfo[])} or
+ * {@link InputMethodManager#displayCompletions(View, CompletionInfo[])} then
+ * a well-behaved IME should never call this on your input
+ * connection, but be ready to deal with misbehaving IMEs without
+ * crashing.</p>
+ *
+ * <p>Calling this method (with a valid {@link CompletionInfo} object)
+ * will cause the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * on the current IME after the batch input is over.
+ * <strong>Editor authors</strong>, for this to happen you need to
+ * make the changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
* @param text The committed completion.
- *
- * @return Returns true on success, false if the input connection is no longer
+ * @return true on success, false if the input connection is no longer
* valid.
*/
public boolean commitCompletion(CompletionInfo text);
/**
- * Commit a correction automatically performed on the raw user's input. A typical example would
- * be to correct typos using a dictionary.
+ * Commit a correction automatically performed on the raw user's input. A
+ * typical example would be to correct typos using a dictionary.
*
- * @param correctionInfo Detailed information about the correction.
+ * <p>Calling this method will cause the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * on the current IME after the batch input is over.
+ * <strong>Editor authors</strong>, for this to happen you need to
+ * make the changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
*
- * @return True on success, false if the input connection is no longer valid.
+ * @param correctionInfo Detailed information about the correction.
+ * @return true on success, false if the input connection is no longer valid.
*/
public boolean commitCorrection(CorrectionInfo correctionInfo);
/**
- * Set the selection of the text editor. To set the cursor position,
- * start and end should have the same value.
- * @return Returns true on success, false if the input connection is no longer
+ * Set the selection of the text editor. To set the cursor
+ * position, start and end should have the same value.
+ *
+ * <p>Since this moves the cursor, calling this method will cause
+ * the editor to call
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * on the current IME after the batch input is over.
+ * <strong>Editor authors</strong>, for this to happen you need to
+ * make the changes known to the input method by calling
+ * {@link InputMethodManager#updateSelection(View, int, int, int, int)},
+ * but be careful to wait until the batch edit is over if one is
+ * in progress.</p>
+ *
+ * <p>This has no effect on the composing region which must stay
+ * unchanged. The order of start and end is not important. In
+ * effect, the region from start to end and the region from end to
+ * start is the same. Editor authors, be ready to accept a start
+ * that is greater than end.</p>
+ *
+ * @param start the character index where the selection should start.
+ * @param end the character index where the selection should end.
+ * @return true on success, false if the input connection is no longer
* valid.
*/
public boolean setSelection(int start, int end);
-
+
/**
* Have the editor perform an action it has said it can do.
- *
+ *
+ * <p>This is typically used by IMEs when the user presses the key
+ * associated with the action.</p>
+ *
* @param editorAction This must be one of the action constants for
* {@link EditorInfo#imeOptions EditorInfo.editorType}, such as
* {@link EditorInfo#IME_ACTION_GO EditorInfo.EDITOR_ACTION_GO}.
- *
- * @return Returns true on success, false if the input connection is no longer
+ * @return true on success, false if the input connection is no longer
* valid.
*/
public boolean performEditorAction(int editorAction);
-
+
/**
- * Perform a context menu action on the field. The given id may be one of:
+ * Perform a context menu action on the field. The given id may be one of:
* {@link android.R.id#selectAll},
* {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
* {@link android.R.id#cut}, {@link android.R.id#copy},
@@ -273,50 +581,82 @@ public interface InputConnection {
* or {@link android.R.id#switchInputMethod}
*/
public boolean performContextMenuAction(int id);
-
+
/**
- * Tell the editor that you are starting a batch of editor operations.
- * The editor will try to avoid sending you updates about its state
- * until {@link #endBatchEdit} is called.
+ * Tell the editor that you are starting a batch of editor
+ * operations. The editor will try to avoid sending you updates
+ * about its state until {@link #endBatchEdit} is called. Batch
+ * edits nest.
+ *
+ * <p><strong>IME authors:</strong> use this to avoid getting
+ * calls to
+ * {@link android.inputmethodservice.InputMethodService#onUpdateSelection(int, int, int, int, int, int)}
+ * corresponding to intermediate state. Also, use this to avoid
+ * flickers that may arise from displaying intermediate state. Be
+ * sure to call {@link #endBatchEdit} for each call to this, or
+ * you may block updates in the editor.</p>
+ *
+ * <p><strong>Editor authors:</strong> while a batch edit is in
+ * progress, take care not to send updates to the input method and
+ * not to update the display. IMEs use this intensively to this
+ * effect. Also please note that batch edits need to nest
+ * correctly.</p>
+ *
+ * @return true if a batch edit is now in progress, false otherwise. Since
+ * this method starts a batch edit, that means it will always return true
+ * unless the input connection is no longer valid.
*/
public boolean beginBatchEdit();
-
+
/**
* Tell the editor that you are done with a batch edit previously
- * initiated with {@link #beginBatchEdit}.
+ * initiated with {@link #beginBatchEdit}. This ends the latest
+ * batch only.
+ *
+ * <p><strong>IME authors:</strong> make sure you call this
+ * exactly once for each call to {@link #beginBatchEdit}.</p>
+ *
+ * <p><strong>Editor authors:</strong> please be careful about
+ * batch edit nesting. Updates still to be held back until the end
+ * of the last batch edit.</p>
+ *
+ * @return true if there is still a batch edit in progress after closing
+ * the latest one (in other words, if the nesting count is > 0), false
+ * otherwise or if the input connection is no longer valid.
*/
public boolean endBatchEdit();
-
+
/**
- * Send a key event to the process that is currently attached through
- * this input connection. The event will be dispatched like a normal
- * key event, to the currently focused; this generally is the view that
- * is providing this InputConnection, but due to the asynchronous nature
- * of this protocol that can not be guaranteed and the focus may have
- * changed by the time the event is received.
- *
- * <p>
- * This method can be used to send key events to the application. For
- * example, an on-screen keyboard may use this method to simulate a hardware
- * keyboard. There are three types of standard keyboards, numeric (12-key),
- * predictive (20-key) and ALPHA (QWERTY). You can specify the keyboard type
- * by specify the device id of the key event.
- *
- * <p>
- * You will usually want to set the flag
- * {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD} on all
- * key event objects you give to this API; the flag will not be set
- * for you.
- *
- * <p>Note that it's discouraged to send such key events in normal operation;
- * this is mainly for use with {@link android.text.InputType#TYPE_NULL} type
- * text fields. Use the {@link #commitText} family of methods to send text
- * to the application instead.
+ * Send a key event to the process that is currently attached
+ * through this input connection. The event will be dispatched
+ * like a normal key event, to the currently focused view; this
+ * generally is the view that is providing this InputConnection,
+ * but due to the asynchronous nature of this protocol that can
+ * not be guaranteed and the focus may have changed by the time
+ * the event is received.
+ *
+ * <p>This method can be used to send key events to the
+ * application. For example, an on-screen keyboard may use this
+ * method to simulate a hardware keyboard. There are three types
+ * of standard keyboards, numeric (12-key), predictive (20-key)
+ * and ALPHA (QWERTY). You can specify the keyboard type by
+ * specify the device id of the key event.</p>
+ *
+ * <p>You will usually want to set the flag
+ * {@link KeyEvent#FLAG_SOFT_KEYBOARD KeyEvent.FLAG_SOFT_KEYBOARD}
+ * on all key event objects you give to this API; the flag will
+ * not be set for you.</p>
+ *
+ * <p>Note that it's discouraged to send such key events in normal
+ * operation; this is mainly for use with
+ * {@link android.text.InputType#TYPE_NULL} type text fields. Use
+ * the {@link #commitText} family of methods to send text to the
+ * application instead.</p>
+ *
* @param event The key event.
- *
- * @return Returns true on success, false if the input connection is no longer
+ * @return true on success, false if the input connection is no longer
* valid.
- *
+ *
* @see KeyEvent
* @see KeyCharacterMap#NUMERIC
* @see KeyCharacterMap#PREDICTIVE
@@ -325,37 +665,46 @@ public interface InputConnection {
public boolean sendKeyEvent(KeyEvent event);
/**
- * Clear the given meta key pressed states in the given input connection.
- *
+ * Clear the given meta key pressed states in the given input
+ * connection.
+ *
+ * <p>This can be used by the IME to clear the meta key states set
+ * by a hardware keyboard with latched meta keys, if the editor
+ * keeps track of these.</p>
+ *
* @param states The states to be cleared, may be one or more bits as
* per {@link KeyEvent#getMetaState() KeyEvent.getMetaState()}.
- *
- * @return Returns true on success, false if the input connection is no longer
+ * @return true on success, false if the input connection is no longer
* valid.
*/
public boolean clearMetaKeyStates(int states);
-
+
/**
- * Called by the IME to tell the client when it switches between fullscreen
- * and normal modes. This will normally be called for you by the standard
- * implementation of {@link android.inputmethodservice.InputMethodService}.
+ * Called by the IME to tell the client when it switches between
+ * fullscreen and normal modes. This will normally be called for
+ * you by the standard implementation of
+ * {@link android.inputmethodservice.InputMethodService}.
+ *
+ * @return true on success, false if the input connection is no longer
+ * valid.
*/
public boolean reportFullscreenMode(boolean enabled);
-
+
/**
- * API to send private commands from an input method to its connected
- * editor. This can be used to provide domain-specific features that are
- * only known between certain input methods and their clients. Note that
- * because the InputConnection protocol is asynchronous, you have no way
- * to get a result back or know if the client understood the command; you
- * can use the information in {@link EditorInfo} to determine if
- * a client supports a particular command.
- *
- * @param action Name of the command to be performed. This <em>must</em>
+ * API to send private commands from an input method to its
+ * connected editor. This can be used to provide domain-specific
+ * features that are only known between certain input methods and
+ * their clients. Note that because the InputConnection protocol
+ * is asynchronous, you have no way to get a result back or know
+ * if the client understood the command; you can use the
+ * information in {@link EditorInfo} to determine if a client
+ * supports a particular command.
+ *
+ * @param action Name of the command to be performed. This <em>must</em>
* be a scoped name, i.e. prefixed with a package name you own, so that
* different developers will not create conflicting commands.
* @param data Any data to include with the command.
- * @return Returns true if the command was sent (whether or not the
+ * @return true if the command was sent (whether or not the
* associated editor understood it), false if the input connection is no longer
* valid.
*/
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 08e30aa..54c2ba5 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -81,6 +81,11 @@ public final class InputMethodInfo implements Parcelable {
private boolean mIsAuxIme;
/**
+ * Cavert: mForceDefault must be false for production. This flag is only for test.
+ */
+ private final boolean mForceDefault;
+
+ /**
* Constructor.
*
* @param context The Context in which we are parsing the input method.
@@ -108,6 +113,7 @@ public final class InputMethodInfo implements Parcelable {
ServiceInfo si = service.serviceInfo;
mId = new ComponentName(si.packageName, si.name).flattenToShortString();
mIsAuxIme = true;
+ mForceDefault = false;
PackageManager pm = context.getPackageManager();
String settingsActivityComponent = null;
@@ -215,13 +221,39 @@ public final class InputMethodInfo implements Parcelable {
mIsAuxIme = source.readInt() == 1;
mService = ResolveInfo.CREATOR.createFromParcel(source);
source.readTypedList(mSubtypes, InputMethodSubtype.CREATOR);
+ mForceDefault = false;
}
/**
- * Temporary API for creating a built-in input method.
+ * Temporary API for creating a built-in input method for test.
*/
public InputMethodInfo(String packageName, String className,
CharSequence label, String settingsActivity) {
+ this(buildDummyResolveInfo(packageName, className, label), false, settingsActivity, null,
+ 0, false);
+ }
+
+ /**
+ * Temporary API for creating a built-in input method for test.
+ * @hide
+ */
+ public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
+ String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
+ boolean forceDefault) {
+ final ServiceInfo si = ri.serviceInfo;
+ mService = ri;
+ mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+ mSettingsActivityName = settingsActivity;
+ mIsDefaultResId = isDefaultResId;
+ mIsAuxIme = isAuxIme;
+ if (subtypes != null) {
+ mSubtypes.addAll(subtypes);
+ }
+ mForceDefault = forceDefault;
+ }
+
+ private static ResolveInfo buildDummyResolveInfo(String packageName, String className,
+ CharSequence label) {
ResolveInfo ri = new ResolveInfo();
ServiceInfo si = new ServiceInfo();
ApplicationInfo ai = new ApplicationInfo();
@@ -234,11 +266,7 @@ public final class InputMethodInfo implements Parcelable {
si.exported = true;
si.nonLocalizedLabel = label;
ri.serviceInfo = si;
- mService = ri;
- mId = new ComponentName(si.packageName, si.name).flattenToShortString();
- mSettingsActivityName = settingsActivity;
- mIsDefaultResId = 0;
- mIsAuxIme = false;
+ return ri;
}
/**
@@ -340,6 +368,22 @@ public final class InputMethodInfo implements Parcelable {
return mIsDefaultResId;
}
+ /**
+ * Return whether or not this ime is a default ime or not.
+ * @hide
+ */
+ public boolean isDefault(Context context) {
+ if (mForceDefault) {
+ return true;
+ }
+ try {
+ final Resources res = context.createPackageContext(getPackageName(), 0).getResources();
+ return res.getBoolean(getIsDefaultResourceId());
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
public void dump(Printer pw, String prefix) {
pw.println(prefix + "mId=" + mId
+ " mSettingsActivityName=" + mSettingsActivityName);
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index d258f4d..4df4734 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -19,7 +19,6 @@ package android.view.inputmethod;
import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInputConnectionWrapper;
import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethodCallback;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
@@ -35,15 +34,20 @@ import android.os.Message;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
-import android.os.SystemClock;
+import android.os.Trace;
import android.text.style.SuggestionSpan;
import android.util.Log;
+import android.util.Pools.Pool;
+import android.util.Pools.SimplePool;
import android.util.PrintWriterPrinter;
import android.util.Printer;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
import android.view.KeyEvent;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewRootImpl;
+import android.util.SparseArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -199,8 +203,9 @@ public final class InputMethodManager {
static final boolean DEBUG = false;
static final String TAG = "InputMethodManager";
- static final Object mInstanceSync = new Object();
- static InputMethodManager mInstance;
+ static final String PENDING_EVENT_COUNTER = "aq:imm";
+
+ static InputMethodManager sInstance;
/**
* @hide Flag for IInputMethodManager.windowGainedFocus: a view in
@@ -231,7 +236,14 @@ public final class InputMethodManager {
*/
static final long INPUT_METHOD_NOT_RESPONDING_TIMEOUT = 2500;
- private static final int MAX_PENDING_EVENT_POOL_SIZE = 4;
+ /** @hide */
+ public static final int DISPATCH_IN_PROGRESS = -1;
+
+ /** @hide */
+ public static final int DISPATCH_NOT_HANDLED = 0;
+
+ /** @hide */
+ public static final int DISPATCH_HANDLED = 1;
final IInputMethodManager mService;
final Looper mMainLooper;
@@ -319,10 +331,11 @@ public final class InputMethodManager {
* The actual instance of the method to make calls on it.
*/
IInputMethodSession mCurMethod;
+ InputChannel mCurChannel;
+ ImeInputEventSender mCurSender;
- PendingEvent mPendingEventPool;
- int mPendingEventPoolSize;
- PendingEvent mFirstPendingEvent;
+ final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20);
+ final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20);
// -----------------------------------------------------------
@@ -330,8 +343,10 @@ public final class InputMethodManager {
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;
-
+ static final int MSG_SEND_INPUT_EVENT = 5;
+ static final int MSG_TIMEOUT_INPUT_EVENT = 6;
+ static final int MSG_FLUSH_INPUT_EVENT = 7;
+
class H extends Handler {
H(Looper looper) {
super(looper, null, true);
@@ -363,9 +378,13 @@ public final class InputMethodManager {
if (mBindSequence < 0 || mBindSequence != res.sequence) {
Log.w(TAG, "Ignoring onBind: cur seq=" + mBindSequence
+ ", given seq=" + res.sequence);
+ if (res.channel != null && res.channel != mCurChannel) {
+ res.channel.dispose();
+ }
return;
}
-
+
+ setInputChannelLocked(res.channel);
mCurMethod = res.method;
mCurId = res.id;
mBindSequence = res.sequence;
@@ -445,15 +464,16 @@ public final class InputMethodManager {
}
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);
+ case MSG_SEND_INPUT_EVENT: {
+ sendInputEventAndReportResultOnMainLooper((PendingEvent)msg.obj);
+ return;
+ }
+ case MSG_TIMEOUT_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, true);
+ return;
+ }
+ case MSG_FLUSH_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, false);
return;
}
}
@@ -482,10 +502,10 @@ public final class InputMethodManager {
}
final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
- @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
// No need to check for dump permission, since we only give this
// interface to the system.
-
CountDownLatch latch = new CountDownLatch(1);
SomeArgs sargs = SomeArgs.obtain();
sargs.arg1 = fd;
@@ -501,47 +521,35 @@ public final class InputMethodManager {
fout.println("Interrupted waiting for dump");
}
}
-
+
+ @Override
public void setUsingInputMethod(boolean state) {
}
-
+
+ @Override
public void onBindMethod(InputBindResult res) {
mH.sendMessage(mH.obtainMessage(MSG_BIND, res));
}
-
+
+ @Override
public void onUnbindMethod(int sequence) {
mH.sendMessage(mH.obtainMessage(MSG_UNBIND, sequence, 0));
}
-
+
+ @Override
public void setActive(boolean active) {
mH.sendMessage(mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, 0));
}
- };
-
- 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);
- }
+ final InputConnection mDummyInputConnection = new BaseInputConnection(this, false);
- @Override
- public void sessionCreated(IInputMethodSession session) {
- // Stub -- not for use in the client.
- }
- };
-
InputMethodManager(IInputMethodManager service, Looper looper) {
mService = service;
mMainLooper = looper;
mH = new H(looper);
mIInputContext = new ControlledInputConnectionWrapper(looper,
mDummyInputConnection, this);
-
- if (mInstance == null) {
- mInstance = this;
- }
}
/**
@@ -549,25 +557,15 @@ public final class InputMethodManager {
* doesn't already exist.
* @hide
*/
- static public InputMethodManager getInstance(Context context) {
- return getInstance(context.getMainLooper());
- }
-
- /**
- * Internally, the input method manager can't be context-dependent, so
- * we have this here for the places that need it.
- * @hide
- */
- static public InputMethodManager getInstance(Looper mainLooper) {
- synchronized (mInstanceSync) {
- if (mInstance != null) {
- return mInstance;
+ public static InputMethodManager getInstance() {
+ synchronized (InputMethodManager.class) {
+ if (sInstance == null) {
+ IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
+ IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
+ sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
- IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
- IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
- mInstance = new InputMethodManager(service, mainLooper);
+ return sInstance;
}
- return mInstance;
}
/**
@@ -575,8 +573,8 @@ public final class InputMethodManager {
* if it exists.
* @hide
*/
- static public InputMethodManager peekInstance() {
- return mInstance;
+ public static InputMethodManager peekInstance() {
+ return sInstance;
}
/** @hide */
@@ -716,11 +714,26 @@ public final class InputMethodManager {
*/
void clearBindingLocked() {
clearConnectionLocked();
+ setInputChannelLocked(null);
mBindSequence = -1;
mCurId = null;
mCurMethod = null;
}
-
+
+ void setInputChannelLocked(InputChannel channel) {
+ if (mCurChannel != channel) {
+ if (mCurSender != null) {
+ flushPendingEventsLocked();
+ mCurSender.dispose();
+ mCurSender = null;
+ }
+ if (mCurChannel != null) {
+ mCurChannel.dispose();
+ }
+ mCurChannel = channel;
+ }
+ }
+
/**
* Reset all of the state associated with a served view being connected
* to an input method
@@ -1090,6 +1103,7 @@ public final class InputMethodManager {
// we need to reschedule our work for over there.
if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
vh.post(new Runnable() {
+ @Override
public void run() {
startInputInner(null, 0, 0, 0);
}
@@ -1161,13 +1175,19 @@ public final class InputMethodManager {
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
if (res != null) {
if (res.id != null) {
+ setInputChannelLocked(res.channel);
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!");
- return true;
+ } else {
+ if (res.channel != null && res.channel != mCurChannel) {
+ res.channel.dispose();
+ }
+ if (mCurMethod == null) {
+ // This means there is no input method available.
+ if (DEBUG) Log.v(TAG, "ABORT input: no input method!");
+ return true;
+ }
}
}
if (mCurMethod != null && mCompletions != null) {
@@ -1561,172 +1581,159 @@ public final class InputMethodManager {
throw new RuntimeException(e);
}
}
-
+
/**
+ * Dispatches an input event to the IME.
+ *
+ * Returns {@link #DISPATCH_HANDLED} if the event was handled.
+ * Returns {@link #DISPATCH_NOT_HANDLED} if the event was not handled.
+ * Returns {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the
+ * callback will be invoked later.
+ *
* @hide
*/
- public int dispatchKeyEvent(Context context, int seq, KeyEvent key,
- FinishedEventCallback callback) {
+ public int dispatchInputEvent(InputEvent event, Object token,
+ FinishedInputEventCallback callback, Handler handler) {
synchronized (mH) {
- if (DEBUG) Log.d(TAG, "dispatchKeyEvent");
-
if (mCurMethod != null) {
- if (key.getAction() == KeyEvent.ACTION_DOWN
- && key.getKeyCode() == KeyEvent.KEYCODE_SYM
- && key.getRepeatCount() == 0) {
- showInputMethodPickerLocked();
- return ViewRootImpl.EVENT_HANDLED;
+ if (event instanceof KeyEvent) {
+ KeyEvent keyEvent = (KeyEvent)event;
+ if (keyEvent.getAction() == KeyEvent.ACTION_DOWN
+ && keyEvent.getKeyCode() == KeyEvent.KEYCODE_SYM
+ && keyEvent.getRepeatCount() == 0) {
+ showInputMethodPickerLocked();
+ return DISPATCH_HANDLED;
+ }
}
- 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 ViewRootImpl.EVENT_IN_PROGRESS;
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e);
+
+ if (DEBUG) Log.v(TAG, "DISPATCH INPUT EVENT: " + mCurMethod);
+
+ PendingEvent p = obtainPendingEventLocked(
+ event, token, mCurId, callback, handler);
+ if (mMainLooper.isCurrentThread()) {
+ // Already running on the IMM thread so we can send the event immediately.
+ return sendInputEventOnMainLooperLocked(p);
}
+
+ // Post the event to the IMM thread.
+ Message msg = mH.obtainMessage(MSG_SEND_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mH.sendMessage(msg);
+ return DISPATCH_IN_PROGRESS;
}
}
- return ViewRootImpl.EVENT_NOT_HANDLED;
+ return DISPATCH_NOT_HANDLED;
}
- /**
- * @hide
- */
- public int dispatchTrackballEvent(Context context, int seq, MotionEvent motion,
- FinishedEventCallback callback) {
+ // Must be called on the main looper
+ void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
+ final boolean handled;
synchronized (mH) {
- if (DEBUG) Log.d(TAG, "dispatchTrackballEvent");
-
- if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
- try {
- if (DEBUG) Log.v(TAG, "DISPATCH TRACKBALL: " + mCurMethod);
- final long startTime = SystemClock.uptimeMillis();
- enqueuePendingEventLocked(startTime, seq, mCurId, callback);
- mCurMethod.dispatchTrackballEvent(seq, motion, mInputMethodCallback);
- return ViewRootImpl.EVENT_IN_PROGRESS;
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e);
- }
+ int result = sendInputEventOnMainLooperLocked(p);
+ if (result == DISPATCH_IN_PROGRESS) {
+ return;
}
+
+ handled = (result == DISPATCH_HANDLED);
}
- return ViewRootImpl.EVENT_NOT_HANDLED;
+
+ invokeFinishedInputEventCallback(p, handled);
}
- /**
- * @hide
- */
- public int dispatchGenericMotionEvent(Context context, int seq, MotionEvent motion,
- FinishedEventCallback callback) {
- synchronized (mH) {
- if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent");
+ // Must be called on the main looper
+ int sendInputEventOnMainLooperLocked(PendingEvent p) {
+ if (mCurChannel != null) {
+ if (mCurSender == null) {
+ mCurSender = new ImeInputEventSender(mCurChannel, mH.getLooper());
+ }
- if (mCurMethod != null && mCurrentTextBoxAttribute != null) {
- try {
- 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 ViewRootImpl.EVENT_IN_PROGRESS;
- } catch (RemoteException e) {
- Log.w(TAG, "IME died: " + mCurId + " dropping generic motion: " + motion, e);
- }
+ final InputEvent event = p.mEvent;
+ final int seq = event.getSequenceNumber();
+ if (mCurSender.sendInputEvent(seq, event)) {
+ mPendingEvents.put(seq, p);
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER,
+ mPendingEvents.size());
+
+ Message msg = mH.obtainMessage(MSG_TIMEOUT_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT);
+ return DISPATCH_IN_PROGRESS;
}
+
+ Log.w(TAG, "Unable to send input event to IME: "
+ + mCurId + " dropping: " + event);
}
- return ViewRootImpl.EVENT_NOT_HANDLED;
+ return DISPATCH_NOT_HANDLED;
}
- void finishedEvent(int seq, boolean handled) {
- final FinishedEventCallback callback;
+ void finishedInputEvent(int seq, boolean handled, boolean timeout) {
+ final PendingEvent p;
synchronized (mH) {
- PendingEvent p = dequeuePendingEventLocked(seq);
- if (p == null) {
+ int index = mPendingEvents.indexOfKey(seq);
+ if (index < 0) {
return; // spurious, event already finished or timed out
}
- mH.removeMessages(MSG_EVENT_TIMEOUT, p);
- callback = p.mCallback;
- recyclePendingEventLocked(p);
- }
- callback.finishedEvent(seq, handled);
- }
- void timeoutEvent(int seq) {
- final FinishedEventCallback callback;
- synchronized (mH) {
- PendingEvent p = dequeuePendingEventLocked(seq);
- if (p == null) {
- return; // spurious, event already finished or timed out
+ p = mPendingEvents.valueAt(index);
+ mPendingEvents.removeAt(index);
+ Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER, mPendingEvents.size());
+
+ if (timeout) {
+ Log.w(TAG, "Timeout waiting for IME to handle input event after "
+ + INPUT_METHOD_NOT_RESPONDING_TIMEOUT + " ms: " + p.mInputMethodId);
+ } else {
+ mH.removeMessages(MSG_TIMEOUT_INPUT_EVENT, p);
}
- 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);
+ invokeFinishedInputEventCallback(p, handled);
}
- private PendingEvent dequeuePendingEventLocked(int seq) {
- PendingEvent p = mFirstPendingEvent;
- if (p == null) {
- return null;
- }
- if (p.mSeq == seq) {
- mFirstPendingEvent = p.mNext;
+ // Assumes the event has already been removed from the queue.
+ void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
+ p.mHandled = handled;
+ if (p.mHandler.getLooper().isCurrentThread()) {
+ // Already running on the callback handler thread so we can send the
+ // callback immediately.
+ p.run();
} else {
- PendingEvent prev;
- do {
- prev = p;
- p = p.mNext;
- if (p == null) {
- return null;
- }
- } while (p.mSeq != seq);
- prev.mNext = p.mNext;
+ // Post the event to the callback handler thread.
+ // In this case, the callback will be responsible for recycling the event.
+ Message msg = Message.obtain(p.mHandler, p);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
}
- 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();
+ private void flushPendingEventsLocked() {
+ mH.removeMessages(MSG_FLUSH_INPUT_EVENT);
+
+ final int count = mPendingEvents.size();
+ for (int i = 0; i < count; i++) {
+ int seq = mPendingEvents.keyAt(i);
+ Message msg = mH.obtainMessage(MSG_FLUSH_INPUT_EVENT, seq, 0);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
}
+ }
- p.mStartTime = startTime;
- p.mSeq = seq;
+ private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
+ String inputMethodId, FinishedInputEventCallback callback, Handler handler) {
+ PendingEvent p = mPendingEventPool.acquire();
+ if (p == null) {
+ p = new PendingEvent();
+ }
+ p.mEvent = event;
+ p.mToken = token;
p.mInputMethodId = inputMethodId;
p.mCallback = callback;
+ p.mHandler = handler;
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;
- }
+ p.recycle();
+ mPendingEventPool.release(p);
}
public void showInputMethodPicker() {
@@ -1938,16 +1945,45 @@ public final class InputMethodManager {
* the IME has been finished.
* @hide
*/
- public interface FinishedEventCallback {
- public void finishedEvent(int seq, boolean handled);
+ public interface FinishedInputEventCallback {
+ public void onFinishedInputEvent(Object token, boolean handled);
}
- private static final class PendingEvent {
- public PendingEvent mNext;
+ private final class ImeInputEventSender extends InputEventSender {
+ public ImeInputEventSender(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEventFinished(int seq, boolean handled) {
+ finishedInputEvent(seq, handled, false);
+ }
+ }
- public long mStartTime;
- public int mSeq;
+ private final class PendingEvent implements Runnable {
+ public InputEvent mEvent;
+ public Object mToken;
public String mInputMethodId;
- public FinishedEventCallback mCallback;
+ public FinishedInputEventCallback mCallback;
+ public Handler mHandler;
+ public boolean mHandled;
+
+ public void recycle() {
+ mEvent = null;
+ mToken = null;
+ mInputMethodId = null;
+ mCallback = null;
+ mHandler = null;
+ mHandled = false;
+ }
+
+ @Override
+ public void run() {
+ mCallback.onFinishedInputEvent(mToken, mHandled);
+
+ synchronized (mH) {
+ recyclePendingEventLocked(this);
+ }
+ }
}
}
diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java
index 008a615..abc078b 100644
--- a/core/java/android/webkit/AccessibilityInjector.java
+++ b/core/java/android/webkit/AccessibilityInjector.java
@@ -318,12 +318,15 @@ class AccessibilityInjector {
/**
* Attempts to handle selection change events when accessibility is using a
* non-JavaScript method.
+ * <p>
+ * This must not be called from the main thread.
*
- * @param selectionString The selection string.
+ * @param selection The selection string.
+ * @param token The selection request token.
*/
- public void handleSelectionChangedIfNecessary(String selectionString) {
+ public void onSelectionStringChangedWebCoreThread(String selection, int token) {
if (mAccessibilityInjectorFallback != null) {
- mAccessibilityInjectorFallback.onSelectionStringChange(selectionString);
+ mAccessibilityInjectorFallback.onSelectionStringChangedWebCoreThread(selection, token);
}
}
@@ -644,6 +647,9 @@ class AccessibilityInjector {
private static class TextToSpeechWrapper {
private static final String WRAP_TAG = TextToSpeechWrapper.class.getSimpleName();
+ /** Lock used to control access to the TextToSpeech object. */
+ private final Object mTtsLock = new Object();
+
private final HashMap<String, String> mTtsParams;
private final TextToSpeech mTextToSpeech;
@@ -681,7 +687,7 @@ class AccessibilityInjector {
@JavascriptInterface
@SuppressWarnings("unused")
public boolean isSpeaking() {
- synchronized (mTextToSpeech) {
+ synchronized (mTtsLock) {
if (!mReady) {
return false;
}
@@ -693,7 +699,7 @@ class AccessibilityInjector {
@JavascriptInterface
@SuppressWarnings("unused")
public int speak(String text, int queueMode, HashMap<String, String> params) {
- synchronized (mTextToSpeech) {
+ synchronized (mTtsLock) {
if (!mReady) {
if (DEBUG) {
Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to speak before TTS init");
@@ -712,7 +718,7 @@ class AccessibilityInjector {
@JavascriptInterface
@SuppressWarnings("unused")
public int stop() {
- synchronized (mTextToSpeech) {
+ synchronized (mTtsLock) {
if (!mReady) {
if (DEBUG) {
Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to stop before initialize");
@@ -730,7 +736,7 @@ class AccessibilityInjector {
@SuppressWarnings("unused")
protected void shutdown() {
- synchronized (mTextToSpeech) {
+ synchronized (mTtsLock) {
if (!mReady) {
if (DEBUG) {
Log.w(WRAP_TAG, "[" + hashCode() + "] Called shutdown before initialize");
@@ -750,7 +756,7 @@ class AccessibilityInjector {
private final OnInitListener mInitListener = new OnInitListener() {
@Override
public void onInit(int status) {
- synchronized (mTextToSpeech) {
+ synchronized (mTtsLock) {
if (!mShutdown && (status == TextToSpeech.SUCCESS)) {
if (DEBUG) {
Log.d(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode()
diff --git a/core/java/android/webkit/AccessibilityInjectorFallback.java b/core/java/android/webkit/AccessibilityInjectorFallback.java
index 783b3db..40cc4e9 100644
--- a/core/java/android/webkit/AccessibilityInjectorFallback.java
+++ b/core/java/android/webkit/AccessibilityInjectorFallback.java
@@ -27,8 +27,9 @@ import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.webkit.WebViewCore.EventHub;
+import com.android.internal.os.SomeArgs;
+
import java.util.ArrayList;
-import java.util.Stack;
/**
* This class injects accessibility into WebViews with disabled JavaScript or
@@ -48,8 +49,7 @@ import java.util.Stack;
* </p>
* The possible actions are invocations to
* {@link #setCurrentAxis(int, boolean, String)}, or
- * {@link #traverseCurrentAxis(int, boolean, String)}
- * {@link #traverseGivenAxis(int, int, boolean, String)}
+ * {@link #traverseGivenAxis(int, int, boolean, String, boolean)}
* {@link #performAxisTransition(int, int, boolean, String)}
* referred via the values of:
* {@link #ACTION_SET_CURRENT_AXIS},
@@ -74,6 +74,9 @@ class AccessibilityInjectorFallback {
private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4;
+ /** Timeout after which asynchronous granular movement is aborted. */
+ private static final int MODIFY_SELECTION_TIMEOUT = 500;
+
// WebView navigation axes from WebViewCore.h, plus an additional axis for
// the default behavior.
private static final int NAVIGATION_AXIS_CHARACTER = 0;
@@ -81,7 +84,8 @@ class AccessibilityInjectorFallback {
private static final int NAVIGATION_AXIS_SENTENCE = 2;
@SuppressWarnings("unused")
private static final int NAVIGATION_AXIS_HEADING = 3;
- private static final int NAVIGATION_AXIS_SIBLING = 5;
+ @SuppressWarnings("unused")
+ private static final int NAVIGATION_AXIS_SIBLING = 4;
@SuppressWarnings("unused")
private static final int NAVIGATION_AXIS_PARENT_FIRST_CHILD = 5;
private static final int NAVIGATION_AXIS_DOCUMENT = 6;
@@ -99,8 +103,11 @@ class AccessibilityInjectorFallback {
private final WebViewClassic mWebView;
private final WebView mWebViewInternal;
- // events scheduled for sending as soon as we receive the selected text
- private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>();
+ // Event scheduled for sending as soon as we receive the selected text.
+ private AccessibilityEvent mScheduledEvent;
+
+ // Token required to send the scheduled event.
+ private int mScheduledToken = 0;
// the current traversal axis
private int mCurrentAxis = 2; // sentence
@@ -114,6 +121,15 @@ class AccessibilityInjectorFallback {
// keep track of last direction
private int mLastDirection;
+ // Lock used for asynchronous selection callback.
+ private final Object mCallbackLock = new Object();
+
+ // Whether the asynchronous selection callback was received.
+ private boolean mCallbackReceived;
+
+ // Whether the asynchronous selection callback succeeded.
+ private boolean mCallbackResult;
+
/**
* Creates a new injector associated with a given {@link WebViewClassic}.
*
@@ -174,8 +190,8 @@ class AccessibilityInjectorFallback {
}
mLastDirection = direction;
sendEvent = (binding.getSecondArgument(i) == 1);
- mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent,
- contentDescription);
+ mLastDownEventHandled = traverseGivenAxis(
+ direction, mCurrentAxis, sendEvent, contentDescription, false);
break;
case ACTION_TRAVERSE_GIVEN_AXIS:
direction = binding.getFirstArgument(i);
@@ -187,7 +203,7 @@ class AccessibilityInjectorFallback {
mLastDirection = direction;
axis = binding.getSecondArgument(i);
sendEvent = (binding.getThirdArgument(i) == 1);
- traverseGivenAxis(direction, axis, sendEvent, contentDescription);
+ traverseGivenAxis(direction, axis, sendEvent, contentDescription, false);
mLastDownEventHandled = true;
break;
case ACTION_PERFORM_AXIS_TRANSITION:
@@ -207,7 +223,7 @@ class AccessibilityInjectorFallback {
mLastDirection = binding.getFirstArgument(i);
sendEvent = (binding.getSecondArgument(i) == 1);
traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR,
- sendEvent, contentDescription);
+ sendEvent, contentDescription, false);
mLastDownEventHandled = false;
} else {
mLastDownEventHandled = true;
@@ -222,8 +238,7 @@ class AccessibilityInjectorFallback {
}
/**
- * Set the current navigation axis which will be used while
- * calling {@link #traverseCurrentAxis(int, boolean, String)}.
+ * Set the current navigation axis.
*
* @param axis The axis to set.
* @param sendEvent Whether to send an accessibility event to
@@ -255,20 +270,6 @@ class AccessibilityInjectorFallback {
}
}
- /**
- * Traverse the document along the current navigation axis.
- *
- * @param direction The direction of traversal.
- * @param sendEvent Whether to send an accessibility event to
- * announce the change.
- * @param contentDescription A description of the performed action.
- * @see #setCurrentAxis(int, boolean, String)
- */
- private boolean traverseCurrentAxis(int direction, boolean sendEvent,
- String contentDescription) {
- return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription);
- }
-
boolean performAccessibilityAction(int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
@@ -276,14 +277,14 @@ class AccessibilityInjectorFallback {
final int direction = getDirectionForAction(action);
final int axis = getAxisForGranularity(arguments.getInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT));
- return traverseGivenAxis(direction, axis, true, null);
+ return traverseGivenAxis(direction, axis, true, null, true);
}
case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: {
final int direction = getDirectionForAction(action);
// TODO: Add support for moving by object.
final int axis = NAVIGATION_AXIS_SENTENCE;
- return traverseGivenAxis(direction, axis, true, null);
+ return traverseGivenAxis(direction, axis, true, null, true);
}
default:
return false;
@@ -293,7 +294,7 @@ class AccessibilityInjectorFallback {
/**
* Returns the {@link WebView}-defined direction for the given
* {@link AccessibilityNodeInfo}-defined action.
- *
+ *
* @param action An accessibility action identifier.
* @return A web view navigation direction.
*/
@@ -313,7 +314,7 @@ class AccessibilityInjectorFallback {
/**
* Returns the {@link WebView}-defined axis for the given
* {@link AccessibilityNodeInfo}-defined granularity.
- *
+ *
* @param granularity An accessibility granularity identifier.
* @return A web view navigation axis.
*/
@@ -345,20 +346,20 @@ class AccessibilityInjectorFallback {
* @param contentDescription A description of the performed action.
*/
private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
- String contentDescription) {
- WebViewCore webViewCore = mWebView.getWebViewCore();
+ String contentDescription, boolean sychronous) {
+ final WebViewCore webViewCore = mWebView.getWebViewCore();
if (webViewCore == null) {
return false;
}
- AccessibilityEvent event = null;
if (sendEvent) {
- event = getPartialyPopulatedAccessibilityEvent(
+ final AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent(
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY);
- // the text will be set upon receiving the selection string
+ // The text will be set upon receiving the selection string.
event.setContentDescription(contentDescription);
+ mScheduledEvent = event;
+ mScheduledToken++;
}
- mScheduledEventStack.push(event);
// if the axis is the default let WebView handle the event which will
// result in cursor ring movement and selection of its content
@@ -366,26 +367,76 @@ class AccessibilityInjectorFallback {
return false;
}
- webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis);
- return true;
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = direction;
+ args.argi2 = axis;
+ args.argi3 = mScheduledToken;
+
+ // If we don't need synchronous results, just return true.
+ if (!sychronous) {
+ webViewCore.sendMessage(EventHub.MODIFY_SELECTION, args);
+ return true;
+ }
+
+ final boolean callbackResult;
+
+ synchronized (mCallbackLock) {
+ mCallbackReceived = false;
+
+ // Asynchronously changes the selection in WebView, which responds by
+ // calling onSelectionStringChanged().
+ webViewCore.sendMessage(EventHub.MODIFY_SELECTION, args);
+
+ try {
+ mCallbackLock.wait(MODIFY_SELECTION_TIMEOUT);
+ } catch (InterruptedException e) {
+ // Do nothing.
+ }
+
+ callbackResult = mCallbackResult;
+ }
+
+ return (mCallbackReceived && callbackResult);
}
- /**
- * Called when the <code>selectionString</code> has changed.
- */
- public void onSelectionStringChange(String selectionString) {
+ /* package */ void onSelectionStringChangedWebCoreThread(
+ final String selection, final int token) {
+ synchronized (mCallbackLock) {
+ mCallbackReceived = true;
+ mCallbackResult = (selection != null);
+ mCallbackLock.notifyAll();
+ }
+
+ // Managing state and sending events must take place on the UI thread.
+ mWebViewInternal.post(new Runnable() {
+ @Override
+ public void run() {
+ onSelectionStringChangedMainThread(selection, token);
+ }
+ });
+ }
+
+ private void onSelectionStringChangedMainThread(String selection, int token) {
if (DEBUG) {
- Log.d(LOG_TAG, "Selection string: " + selectionString);
+ Log.d(LOG_TAG, "Selection string: " + selection);
}
- mIsLastSelectionStringNull = (selectionString == null);
- if (mScheduledEventStack.isEmpty()) {
+
+ if (token != mScheduledToken) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Selection string has incorrect token: " + token);
+ }
return;
}
- AccessibilityEvent event = mScheduledEventStack.pop();
- if ((event != null) && (selectionString != null)) {
- event.getText().add(selectionString);
+
+ mIsLastSelectionStringNull = (selection == null);
+
+ final AccessibilityEvent event = mScheduledEvent;
+ mScheduledEvent = null;
+
+ if ((event != null) && (selection != null)) {
+ event.getText().add(selection);
event.setFromIndex(0);
- event.setToIndex(selectionString.length());
+ event.setToIndex(selection.length());
sendAccessibilityEvent(event);
}
}
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index f0cc39b..fbb5439 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -227,8 +227,6 @@ class BrowserFrame extends Handler {
} else {
sJavaBridge.setCacheSize(4 * 1024 * 1024);
}
- // initialize CacheManager
- CacheManager.init(appContext);
// create CookieSyncManager with current Context
CookieSyncManager.createInstance(appContext);
// create PluginManager with current Context
@@ -503,11 +501,12 @@ class BrowserFrame extends Handler {
WebAddress uri = new WebAddress(item.getUrl());
String schemePlusHost = uri.getScheme() + SCHEME_HOST_DELIMITER +
uri.getHost();
- String[] up = mDatabase.getUsernamePassword(
- schemePlusHost);
+ WebViewDatabaseClassic database =
+ WebViewDatabaseClassic.getInstance(mContext);
+ String[] up = database.getUsernamePassword(schemePlusHost);
if (up == null) { // no row found, try again using the legacy method
schemePlusHost = uri.getScheme() + uri.getHost();
- up = mDatabase.getUsernamePassword(schemePlusHost);
+ up = database.getUsernamePassword(schemePlusHost);
}
if (up != null && up[0] != null) {
setUsernamePassword(up[0], up[1]);
@@ -764,13 +763,16 @@ class BrowserFrame extends Handler {
return null;
}
} else if (url.startsWith(ANDROID_ASSET)) {
- url = url.replaceFirst(ANDROID_ASSET, "");
+ String assetUrl = url.replaceFirst(ANDROID_ASSET, "");
try {
AssetManager assets = mContext.getAssets();
- Uri uri = Uri.parse(url);
+ Uri uri = Uri.parse(assetUrl);
return assets.open(uri.getPath(), AssetManager.ACCESS_STREAMING);
} catch (IOException e) {
return null;
+ } catch (Exception e) {
+ Log.w(LOGTAG, "Problem loading url: " + url, e);
+ return null;
}
} else if (mSettings.getAllowContentAccess() &&
url.startsWith(ANDROID_CONTENT)) {
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index 52f41e6..bbd3f2b 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -45,14 +45,6 @@ import java.util.Map;
// CacheManager may only be used if your activity contains a WebView.
@Deprecated
public final class CacheManager {
-
- private static final String LOGTAG = "cache";
-
- static final String HEADER_KEY_IFMODIFIEDSINCE = "if-modified-since";
- static final String HEADER_KEY_IFNONEMATCH = "if-none-match";
-
- private static File mBaseDir;
-
/**
* Represents a resource stored in the HTTP cache. Instances of this class
* can be obtained by calling
@@ -239,39 +231,23 @@ public final class CacheManager {
}
/**
- * Initializes the HTTP cache. This method must be called before any
- * CacheManager methods are used. Note that this is called automatically
- * when a {@link WebView} is created.
- *
- * @param context the application context
- */
- static void init(Context context) {
- // This isn't actually where the real cache lives, but where we put files for the
- // purpose of getCacheFile().
- mBaseDir = new File(context.getCacheDir(), "webviewCacheChromiumStaging");
- if (!mBaseDir.exists()) {
- mBaseDir.mkdirs();
- }
- }
-
- /**
* Gets the base directory in which the files used to store the contents of
* cache entries are placed. See
* {@link CacheManager.CacheResult#getLocalPath CacheManager.CacheResult.getLocalPath()}.
*
* @return the base directory of the cache
- * @deprecated Access to the HTTP cache will be removed in a future release.
+ * @deprecated This method no longer has any effect and always returns null.
*/
@Deprecated
public static File getCacheFileBaseDir() {
- return mBaseDir;
+ return null;
}
/**
* Gets whether the HTTP cache is disabled.
*
* @return true if the HTTP cache is disabled
- * @deprecated Access to the HTTP cache will be removed in a future release.
+ * @deprecated This method no longer has any effect and always returns false.
*/
@Deprecated
public static boolean cacheDisabled() {
@@ -314,73 +290,11 @@ public final class CacheManager {
* @param headers a map from HTTP header name to value, to be populated
* for the returned cache entry
* @return the cache entry for the specified URL
- * @deprecated Access to the HTTP cache will be removed in a future release.
+ * @deprecated This method no longer has any effect and always returns null.
*/
@Deprecated
public static CacheResult getCacheFile(String url,
Map<String, String> headers) {
- return getCacheFile(url, 0, headers);
- }
-
- static CacheResult getCacheFile(String url, long postIdentifier,
- Map<String, String> headers) {
- CacheResult result = nativeGetCacheResult(url);
- if (result == null) {
- return null;
- }
- // A temporary local file will have been created native side and localPath set
- // appropriately.
- File src = new File(mBaseDir, result.localPath);
- try {
- // Open the file here so that even if it is deleted, the content
- // is still readable by the caller until close() is called.
- result.inStream = new FileInputStream(src);
- } catch (FileNotFoundException e) {
- Log.v(LOGTAG, "getCacheFile(): Failed to open file: " + e);
- // TODO: The files in the cache directory can be removed by the
- // system. If it is gone, what should we do?
- return null;
- }
-
- // A null value for headers is used by CACHE_MODE_CACHE_ONLY to imply
- // that we should provide the cache result even if it is expired.
- // Note that a negative expires value means a time in the far future.
- if (headers != null && result.expires >= 0
- && result.expires <= System.currentTimeMillis()) {
- if (result.lastModified == null && result.etag == null) {
- return null;
- }
- // Return HEADER_KEY_IFNONEMATCH or HEADER_KEY_IFMODIFIEDSINCE
- // for requesting validation.
- if (result.etag != null) {
- headers.put(HEADER_KEY_IFNONEMATCH, result.etag);
- }
- if (result.lastModified != null) {
- headers.put(HEADER_KEY_IFMODIFIEDSINCE, result.lastModified);
- }
- }
-
- if (DebugFlags.CACHE_MANAGER) {
- Log.v(LOGTAG, "getCacheFile for url " + url);
- }
-
- return result;
- }
-
- /**
- * Given a URL and its full headers, gets a CacheResult if a local cache
- * can be stored. Otherwise returns null. The mimetype is passed in so that
- * the function can use the mimetype that will be passed to WebCore which
- * could be different from the mimetype defined in the headers.
- * forceCache is for out-of-package callers to force creation of a
- * CacheResult, and is used to supply surrogate responses for URL
- * interception.
- *
- * @return a CacheResult for a given URL
- */
- static CacheResult createCacheFile(String url, int statusCode,
- Headers headers, String mimeType, boolean forceCache) {
- // This method is public but hidden. We break functionality.
return null;
}
@@ -424,36 +338,4 @@ public final class CacheManager {
// use, we should already have thrown an exception above.
assert false;
}
-
- /**
- * Removes all cache files.
- *
- * @return whether the removal succeeded
- */
- static boolean removeAllCacheFiles() {
- // delete cache files in a separate thread to not block UI.
- final Runnable clearCache = new Runnable() {
- public void run() {
- // delete all cache files
- try {
- String[] files = mBaseDir.list();
- // if mBaseDir doesn't exist, files can be null.
- if (files != null) {
- for (int i = 0; i < files.length; i++) {
- File f = new File(mBaseDir, files[i]);
- if (!f.delete()) {
- Log.e(LOGTAG, f.getPath() + " delete failed.");
- }
- }
- }
- } catch (SecurityException e) {
- // Ignore SecurityExceptions.
- }
- }
- };
- new Thread(clearCache).start();
- return true;
- }
-
- private static native CacheResult nativeGetCacheResult(String url);
}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index a326da2..312af71 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -17,10 +17,8 @@
package android.webkit;
import android.app.Activity;
-import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
@@ -33,10 +31,6 @@ import android.os.SystemClock;
import android.provider.Browser;
import android.util.Log;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.EditText;
-import android.widget.TextView;
import com.android.internal.R;
import java.net.MalformedURLException;
@@ -92,10 +86,7 @@ class CallbackProxy extends Handler {
private static final int CREATE_WINDOW = 109;
private static final int CLOSE_WINDOW = 110;
private static final int SAVE_PASSWORD = 111;
- private static final int JS_ALERT = 112;
- private static final int JS_CONFIRM = 113;
- private static final int JS_PROMPT = 114;
- private static final int JS_UNLOAD = 115;
+ private static final int JS_DIALOG = 112;
private static final int ASYNC_KEYEVENTS = 116;
private static final int DOWNLOAD_FILE = 118;
private static final int REPORT_ERROR = 119;
@@ -566,188 +557,12 @@ class CallbackProxy extends Handler {
}
break;
- case JS_ALERT:
+ case JS_DIALOG:
if (mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
- final JsResult res = receiver.mJsResult;
- String message = msg.getData().getString("message");
- String url = msg.getData().getString("url");
- if (!mWebChromeClient.onJsAlert(mWebView.getWebView(), url, message,
- res)) {
- if (!canShowAlertDialog()) {
- res.cancel();
- receiver.setReady();
- break;
- }
- new AlertDialog.Builder(mContext)
- .setTitle(getJsDialogTitle(url))
- .setMessage(message)
- .setPositiveButton(R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(
- DialogInterface dialog,
- int which) {
- res.confirm();
- }
- })
- .setOnCancelListener(
- new DialogInterface.OnCancelListener() {
- public void onCancel(
- DialogInterface dialog) {
- res.cancel();
- }
- })
- .show();
- }
- receiver.setReady();
- }
- break;
-
- case JS_CONFIRM:
- if (mWebChromeClient != null) {
- final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
- final JsResult res = receiver.mJsResult;
- String message = msg.getData().getString("message");
- String url = msg.getData().getString("url");
- if (!mWebChromeClient.onJsConfirm(mWebView.getWebView(), url, message,
- res)) {
- if (!canShowAlertDialog()) {
- res.cancel();
- receiver.setReady();
- break;
- }
- new AlertDialog.Builder(mContext)
- .setTitle(getJsDialogTitle(url))
- .setMessage(message)
- .setPositiveButton(R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(
- DialogInterface dialog,
- int which) {
- res.confirm();
- }})
- .setNegativeButton(R.string.cancel,
- new DialogInterface.OnClickListener() {
- public void onClick(
- DialogInterface dialog,
- int which) {
- res.cancel();
- }})
- .setOnCancelListener(
- new DialogInterface.OnCancelListener() {
- public void onCancel(
- DialogInterface dialog) {
- res.cancel();
- }
- })
- .show();
- }
- // Tell the JsResult that it is ready for client
- // interaction.
- receiver.setReady();
- }
- break;
-
- case JS_PROMPT:
- if (mWebChromeClient != null) {
- final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
- final JsPromptResult res = receiver.mJsResult;
- String message = msg.getData().getString("message");
- String defaultVal = msg.getData().getString("default");
- String url = msg.getData().getString("url");
- if (!mWebChromeClient.onJsPrompt(mWebView.getWebView(), url, message,
- defaultVal, res)) {
- if (!canShowAlertDialog()) {
- res.cancel();
- receiver.setReady();
- break;
- }
- final LayoutInflater factory = LayoutInflater
- .from(mContext);
- final View view = factory.inflate(R.layout.js_prompt,
- null);
- final EditText v = (EditText) view
- .findViewById(R.id.value);
- v.setText(defaultVal);
- ((TextView) view.findViewById(R.id.message))
- .setText(message);
- new AlertDialog.Builder(mContext)
- .setTitle(getJsDialogTitle(url))
- .setView(view)
- .setPositiveButton(R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(
- DialogInterface dialog,
- int whichButton) {
- res.confirm(v.getText()
- .toString());
- }
- })
- .setNegativeButton(R.string.cancel,
- new DialogInterface.OnClickListener() {
- public void onClick(
- DialogInterface dialog,
- int whichButton) {
- res.cancel();
- }
- })
- .setOnCancelListener(
- new DialogInterface.OnCancelListener() {
- public void onCancel(
- DialogInterface dialog) {
- res.cancel();
- }
- })
- .show();
- }
- // Tell the JsResult that it is ready for client
- // interaction.
- receiver.setReady();
- }
- break;
-
- case JS_UNLOAD:
- if (mWebChromeClient != null) {
- final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
- final JsResult res = receiver.mJsResult;
- String message = msg.getData().getString("message");
- String url = msg.getData().getString("url");
- if (!mWebChromeClient.onJsBeforeUnload(mWebView.getWebView(), url,
- message, res)) {
- if (!canShowAlertDialog()) {
- res.cancel();
- receiver.setReady();
- break;
- }
- final String m = mContext.getString(
- R.string.js_dialog_before_unload, message);
- new AlertDialog.Builder(mContext)
- .setMessage(m)
- .setPositiveButton(R.string.ok,
- new DialogInterface.OnClickListener() {
- public void onClick(
- DialogInterface dialog,
- int which) {
- res.confirm();
- }
- })
- .setNegativeButton(R.string.cancel,
- new DialogInterface.OnClickListener() {
- public void onClick(
- DialogInterface dialog,
- int which) {
- res.cancel();
- }
- })
- .setOnCancelListener(
- new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(
- DialogInterface dialog) {
- res.cancel();
- }
- })
- .show();
+ JsDialogHelper helper = new JsDialogHelper(receiver.mJsResult, msg);
+ if (!helper.invokeCallback(mWebChromeClient, mWebView.getWebView())) {
+ helper.showDialog(mContext);
}
receiver.setReady();
}
@@ -757,7 +572,7 @@ class CallbackProxy extends Handler {
if(mWebChromeClient != null) {
final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
final JsResult res = receiver.mJsResult;
- if(mWebChromeClient.onJsTimeout()) {
+ if (mWebChromeClient.onJsTimeout()) {
res.confirm();
} else {
res.cancel();
@@ -895,24 +710,6 @@ class CallbackProxy extends Handler {
sendMessage(obtainMessage(SWITCH_OUT_HISTORY));
}
- private String getJsDialogTitle(String url) {
- String title = url;
- if (URLUtil.isDataUrl(url)) {
- // For data: urls, we just display 'JavaScript' similar to Safari.
- title = mContext.getString(R.string.js_dialog_title_default);
- } else {
- try {
- URL aUrl = new URL(url);
- // For example: "The page at 'http://www.mit.edu' says:"
- title = mContext.getString(R.string.js_dialog_title,
- aUrl.getProtocol() + "://" + aUrl.getHost());
- } catch (MalformedURLException ex) {
- // do nothing. just use the url as the title
- }
- }
- return title;
- }
-
//--------------------------------------------------------------------------
// WebViewClient functions.
// NOTE: shouldOverrideKeyEvent is never called from the WebCore thread so
@@ -1332,9 +1129,10 @@ class CallbackProxy extends Handler {
return;
}
JsResultReceiver result = new JsResultReceiver();
- Message alert = obtainMessage(JS_ALERT, result);
+ Message alert = obtainMessage(JS_DIALOG, result);
alert.getData().putString("message", message);
alert.getData().putString("url", url);
+ alert.getData().putInt("type", JsDialogHelper.ALERT);
sendMessageToUiThreadSync(alert);
}
@@ -1345,9 +1143,10 @@ class CallbackProxy extends Handler {
return false;
}
JsResultReceiver result = new JsResultReceiver();
- Message confirm = obtainMessage(JS_CONFIRM, result);
+ Message confirm = obtainMessage(JS_DIALOG, result);
confirm.getData().putString("message", message);
confirm.getData().putString("url", url);
+ confirm.getData().putInt("type", JsDialogHelper.CONFIRM);
sendMessageToUiThreadSync(confirm);
return result.mJsResult.getResult();
}
@@ -1359,10 +1158,11 @@ class CallbackProxy extends Handler {
return null;
}
JsResultReceiver result = new JsResultReceiver();
- Message prompt = obtainMessage(JS_PROMPT, result);
+ Message prompt = obtainMessage(JS_DIALOG, result);
prompt.getData().putString("message", message);
prompt.getData().putString("default", defaultValue);
prompt.getData().putString("url", url);
+ prompt.getData().putInt("type", JsDialogHelper.PROMPT);
sendMessageToUiThreadSync(prompt);
return result.mJsResult.getStringResult();
}
@@ -1374,10 +1174,11 @@ class CallbackProxy extends Handler {
return true;
}
JsResultReceiver result = new JsResultReceiver();
- Message confirm = obtainMessage(JS_UNLOAD, result);
- confirm.getData().putString("message", message);
- confirm.getData().putString("url", url);
- sendMessageToUiThreadSync(confirm);
+ Message unload = obtainMessage(JS_DIALOG, result);
+ unload.getData().putString("message", message);
+ unload.getData().putString("url", url);
+ unload.getData().putInt("type", JsDialogHelper.UNLOAD);
+ sendMessageToUiThreadSync(unload);
return result.mJsResult.getResult();
}
@@ -1595,16 +1396,6 @@ class CallbackProxy extends Handler {
sendMessage(msg);
}
- boolean canShowAlertDialog() {
- // We can only display the alert dialog if mContext is
- // an Activity context.
- // FIXME: Should we display dialogs if mContext does
- // not have the window focus (e.g. if the user is viewing
- // another Activity when the alert should be displayed?
- // See bug 3166409
- return mContext instanceof Activity;
- }
-
private synchronized void sendMessageToUiThreadSync(Message msg) {
sendMessage(msg);
WebCoreThreadWatchdog.pause();
diff --git a/core/java/android/webkit/EventLogTags.logtags b/core/java/android/webkit/EventLogTags.logtags
index 082a437..b0b5493 100644
--- a/core/java/android/webkit/EventLogTags.logtags
+++ b/core/java/android/webkit/EventLogTags.logtags
@@ -8,4 +8,3 @@ option java_package android.webkit;
# 70103- used by the browser app itself
70150 browser_snap_center
-70151 browser_text_size_change (oldSize|1|5), (newSize|1|5)
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
index 6a627e1..c68b450 100644
--- a/core/java/android/webkit/FindActionModeCallback.java
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -33,12 +33,15 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
-class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
- View.OnClickListener {
+/**
+ * @hide
+ */
+public class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
+ View.OnClickListener, WebView.FindListener {
private View mCustomView;
private EditText mEditText;
private TextView mMatches;
- private WebViewClassic mWebView;
+ private WebView mWebView;
private InputMethodManager mInput;
private Resources mResources;
private boolean mMatchesFound;
@@ -46,7 +49,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
private int mActiveMatchIndex;
private ActionMode mActionMode;
- FindActionModeCallback(Context context) {
+ public FindActionModeCallback(Context context) {
mCustomView = LayoutInflater.from(context).inflate(
com.android.internal.R.layout.webview_find, null);
mEditText = (EditText) mCustomView.findViewById(
@@ -61,7 +64,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
mResources = context.getResources();
}
- void finish() {
+ public void finish() {
mActionMode.finish();
}
@@ -69,7 +72,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
* Place text in the text field so it can be searched for. Need to press
* the find next or find previous button to find all of the matches.
*/
- void setText(String text) {
+ public void setText(String text) {
mEditText.setText(text);
Spannable span = (Spannable) mEditText.getText();
int length = span.length();
@@ -84,15 +87,23 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
}
/*
- * Set the WebView to search. Must be non null, and set before calling
- * startActionMode.
+ * Set the WebView to search. Must be non null.
*/
- void setWebView(WebViewClassic webView) {
+ public void setWebView(WebView webView) {
if (null == webView) {
throw new AssertionError("WebView supplied to "
+ "FindActionModeCallback cannot be null");
}
mWebView = webView;
+ mWebView.setFindDialogFindListener(this);
+ }
+
+ @Override
+ public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
+ boolean isDoneCounting) {
+ if (isDoneCounting) {
+ updateMatchCount(activeMatchOrdinal, numberOfMatches, numberOfMatches == 0);
+ }
}
/*
@@ -121,7 +132,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
/*
* Highlight all the instances of the string from mEditText in mWebView.
*/
- void findAll() {
+ public void findAll() {
if (mWebView == null) {
throw new AssertionError(
"No WebView for FindActionModeCallback::findAll");
@@ -208,7 +219,8 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
mWebView.notifyFindDialogDismissed();
- mInput.hideSoftInputFromWindow(mWebView.getWebView().getWindowToken(), 0);
+ mWebView.setFindDialogFindListener(null);
+ mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
}
@Override
@@ -222,7 +234,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
throw new AssertionError(
"No WebView for FindActionModeCallback::onActionItemClicked");
}
- mInput.hideSoftInputFromWindow(mWebView.getWebView().getWindowToken(), 0);
+ mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
switch(item.getItemId()) {
case com.android.internal.R.id.find_prev:
findNext(false);
diff --git a/core/java/android/webkit/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java
index 9c0f754..bc3d035 100644
--- a/core/java/android/webkit/GeolocationPermissions.java
+++ b/core/java/android/webkit/GeolocationPermissions.java
@@ -61,7 +61,8 @@ public class GeolocationPermissions {
};
/**
- * Gets the singleton instance of this class.
+ * Gets the singleton instance of this class. This method cannot be
+ * called before the application instantiates a {@link WebView} instance.
*
* @return the singleton {@link GeolocationPermissions} instance
*/
diff --git a/core/java/android/webkit/HTML5Audio.java b/core/java/android/webkit/HTML5Audio.java
index 684ec07..17eb2df 100644
--- a/core/java/android/webkit/HTML5Audio.java
+++ b/core/java/android/webkit/HTML5Audio.java
@@ -19,6 +19,7 @@ package android.webkit;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -84,6 +85,7 @@ class HTML5Audio extends Handler
// See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
private Timer mTimer;
private final class TimeupdateTask extends TimerTask {
+ @Override
public void run() {
HTML5Audio.this.obtainMessage(TIMEUPDATE).sendToTarget();
}
@@ -139,11 +141,13 @@ class HTML5Audio extends Handler
// (i.e. the webviewcore thread here)
// MediaPlayer.OnBufferingUpdateListener
+ @Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
nativeOnBuffering(percent, mNativePointer);
}
// MediaPlayer.OnCompletionListener;
+ @Override
public void onCompletion(MediaPlayer mp) {
mState = COMPLETE;
mProcessingOnEnd = true;
@@ -156,6 +160,7 @@ class HTML5Audio extends Handler
}
// MediaPlayer.OnErrorListener
+ @Override
public boolean onError(MediaPlayer mp, int what, int extra) {
mState = ERROR;
resetMediaPlayer();
@@ -164,6 +169,7 @@ class HTML5Audio extends Handler
}
// MediaPlayer.OnPreparedListener
+ @Override
public void onPrepared(MediaPlayer mp) {
mState = PREPARED;
if (mTimer != null) {
@@ -178,6 +184,7 @@ class HTML5Audio extends Handler
}
// MediaPlayer.OnSeekCompleteListener
+ @Override
public void onSeekComplete(MediaPlayer mp) {
nativeOnTimeupdate(mp.getCurrentPosition(), mNativePointer);
}
@@ -231,7 +238,7 @@ class HTML5Audio extends Handler
headers.put(HIDE_URL_LOGS, "true");
}
- mMediaPlayer.setDataSource(url, headers);
+ mMediaPlayer.setDataSource(mContext, Uri.parse(url), headers);
mState = INITIALIZED;
mMediaPlayer.prepareAsync();
} catch (IOException e) {
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
index 9b93805..b52218d 100644
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ b/core/java/android/webkit/HTML5VideoFullScreen.java
@@ -341,6 +341,14 @@ public class HTML5VideoFullScreen extends HTML5VideoView
}
@Override
+ public int getAudioSessionId() {
+ if (mPlayer == null) {
+ return 0;
+ }
+ return mPlayer.getAudioSessionId();
+ }
+
+ @Override
public void showControllerInFullScreen() {
if (mMediaController != null) {
mMediaController.show(0);
diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java
index 296d960..ee3b369 100644
--- a/core/java/android/webkit/HttpAuthHandler.java
+++ b/core/java/android/webkit/HttpAuthHandler.java
@@ -40,7 +40,7 @@ public class HttpAuthHandler extends Handler {
* previously been rejected by the server for the current request.
*
* @return whether the credentials are suitable for use
- * @see Webview#getHttpAuthUsernamePassword
+ * @see WebView#getHttpAuthUsernamePassword
*/
public boolean useHttpAuthUsernamePassword() {
return false;
diff --git a/core/java/android/webkit/JsDialogHelper.java b/core/java/android/webkit/JsDialogHelper.java
new file mode 100644
index 0000000..bb0339e
--- /dev/null
+++ b/core/java/android/webkit/JsDialogHelper.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Message;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Helper class to create JavaScript dialogs. It is used by
+ * different WebView implementations.
+ *
+ * @hide Helper class for internal use
+ */
+public class JsDialogHelper {
+
+ private static final String TAG = "JsDialogHelper";
+
+ // Dialog types
+ public static final int ALERT = 1;
+ public static final int CONFIRM = 2;
+ public static final int PROMPT = 3;
+ public static final int UNLOAD = 4;
+
+ private final String mDefaultValue;
+ private final JsPromptResult mResult;
+ private final String mMessage;
+ private final int mType;
+ private final String mUrl;
+
+ public JsDialogHelper(JsPromptResult result, int type, String defaultValue, String message,
+ String url) {
+ mResult = result;
+ mDefaultValue = defaultValue;
+ mMessage = message;
+ mType = type;
+ mUrl = url;
+ }
+
+ public JsDialogHelper(JsPromptResult result, Message msg) {
+ mResult = result;
+ mDefaultValue = msg.getData().getString("default");
+ mMessage = msg.getData().getString("message");
+ mType = msg.getData().getInt("type");
+ mUrl = msg.getData().getString("url");
+ }
+
+ public boolean invokeCallback(WebChromeClient client, WebView webView) {
+ switch (mType) {
+ case ALERT:
+ return client.onJsAlert(webView, mUrl, mMessage, mResult);
+ case CONFIRM:
+ return client.onJsConfirm(webView, mUrl, mMessage, mResult);
+ case UNLOAD:
+ return client.onJsBeforeUnload(webView, mUrl, mMessage, mResult);
+ case PROMPT:
+ return client.onJsPrompt(webView, mUrl, mMessage, mDefaultValue, mResult);
+ default:
+ throw new IllegalArgumentException("Unexpected type: " + mType);
+ }
+ }
+
+ public void showDialog(Context context) {
+ if (!canShowAlertDialog(context)) {
+ Log.w(TAG, "Cannot create a dialog, the WebView context is not an Activity");
+ mResult.cancel();
+ return;
+ }
+
+ String title, displayMessage;
+ int positiveTextId, negativeTextId;
+ if (mType == UNLOAD) {
+ title = context.getString(com.android.internal.R.string.js_dialog_before_unload_title);
+ displayMessage = context.getString(
+ com.android.internal.R.string.js_dialog_before_unload, mMessage);
+ positiveTextId = com.android.internal.R.string.js_dialog_before_unload_positive_button;
+ negativeTextId = com.android.internal.R.string.js_dialog_before_unload_negative_button;
+ } else {
+ title = getJsDialogTitle(context);
+ displayMessage = mMessage;
+ positiveTextId = com.android.internal.R.string.ok;
+ negativeTextId = com.android.internal.R.string.cancel;
+ }
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(title);
+ builder.setOnCancelListener(new CancelListener());
+ if (mType != PROMPT) {
+ builder.setMessage(displayMessage);
+ builder.setPositiveButton(positiveTextId, new PositiveListener(null));
+ } else {
+ final View view = LayoutInflater.from(context).inflate(
+ com.android.internal.R.layout.js_prompt, null);
+ EditText edit = ((EditText) view.findViewById(com.android.internal.R.id.value));
+ edit.setText(mDefaultValue);
+ builder.setPositiveButton(positiveTextId, new PositiveListener(edit));
+ ((TextView) view.findViewById(com.android.internal.R.id.message)).setText(mMessage);
+ builder.setView(view);
+ }
+ if (mType != ALERT) {
+ builder.setNegativeButton(negativeTextId, new CancelListener());
+ }
+ builder.show();
+ }
+
+ private class CancelListener implements DialogInterface.OnCancelListener,
+ DialogInterface.OnClickListener {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ mResult.cancel();
+ }
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mResult.cancel();
+ }
+ }
+
+ private class PositiveListener implements DialogInterface.OnClickListener {
+ private final EditText mEdit;
+
+ public PositiveListener(EditText edit) {
+ mEdit = edit;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (mEdit == null) {
+ mResult.confirm();
+ } else {
+ mResult.confirm(mEdit.getText().toString());
+ }
+ }
+ }
+
+ private String getJsDialogTitle(Context context) {
+ String title = mUrl;
+ if (URLUtil.isDataUrl(mUrl)) {
+ // For data: urls, we just display 'JavaScript' similar to Chrome.
+ title = context.getString(com.android.internal.R.string.js_dialog_title_default);
+ } else {
+ try {
+ URL alertUrl = new URL(mUrl);
+ // For example: "The page at 'http://www.mit.edu' says:"
+ title = context.getString(com.android.internal.R.string.js_dialog_title,
+ alertUrl.getProtocol() + "://" + alertUrl.getHost());
+ } catch (MalformedURLException ex) {
+ // do nothing. just use the url as the title
+ }
+ }
+ return title;
+ }
+
+ private static boolean canShowAlertDialog(Context context) {
+ // We can only display the alert dialog if mContext is
+ // an Activity context.
+ // FIXME: Should we display dialogs if mContext does
+ // not have the window focus (e.g. if the user is viewing
+ // another Activity when the alert should be displayed) ?
+ // See bug 3166409
+ return context instanceof Activity;
+ }
+}
diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java
index 3a43950..af31544 100644
--- a/core/java/android/webkit/SslErrorHandler.java
+++ b/core/java/android/webkit/SslErrorHandler.java
@@ -19,9 +19,11 @@ package android.webkit;
import android.os.Handler;
/**
- * SslErrorHandler: class responsible for handling SSL errors.
- * This class is passed as a parameter to BrowserCallback.displaySslErrorDialog
- * and is meant to receive the user's response.
+ * Represents a request for handling an SSL error. Instances of this class are
+ * created by the WebView and passed to
+ * {@link WebViewClient#onReceivedSslError}. The host application must call
+ * either {@link #proceed} or {@link #cancel} to set the WebView's response
+ * to the request.
*/
public class SslErrorHandler extends Handler {
diff --git a/core/java/android/webkit/ViewStateSerializer.java b/core/java/android/webkit/ViewStateSerializer.java
index 096d4cda..1d44b96 100644
--- a/core/java/android/webkit/ViewStateSerializer.java
+++ b/core/java/android/webkit/ViewStateSerializer.java
@@ -31,7 +31,8 @@ class ViewStateSerializer {
private static final int WORKING_STREAM_STORAGE = 16 * 1024;
- static final int VERSION = 1;
+ // VERSION = 1 was for pictures encoded using a previous copy of libskia
+ static final int VERSION = 2;
static boolean serializeViewState(OutputStream stream, DrawData draw)
throws IOException {
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index e93db09..21b0578 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -69,7 +69,9 @@ public class WebChromeClient {
/**
* Notify the host application that the current page would
- * like to show a custom View.
+ * like to show a custom View. This is used for Fullscreen
+ * video playback; see "HTML5 Video support" documentation on
+ * {@link WebView}.
* @param view is the View object to be shown.
* @param callback is the callback to be invoked if and when the view
* is dismissed.
@@ -84,7 +86,10 @@ public class WebChromeClient {
* {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
* @param callback is the callback to be invoked if and when the view
* is dismissed.
+ * @deprecated This method supports the obsolete plugin mechanism,
+ * and will not be invoked in future
*/
+ @Deprecated
public void onShowCustomView(View view, int requestedOrientation,
CustomViewCallback callback) {};
diff --git a/core/java/android/webkit/WebIconDatabase.java b/core/java/android/webkit/WebIconDatabase.java
index 99f20ff..e574593 100644
--- a/core/java/android/webkit/WebIconDatabase.java
+++ b/core/java/android/webkit/WebIconDatabase.java
@@ -25,11 +25,19 @@ import android.graphics.Bitmap;
* and WebView.getIconDatabase() will return a WebIconDatabase object. This
* WebIconDatabase object is a single instance and all methods operate on that
* single object.
+ * The main use-case for this class is calling {@link #open}
+ * to enable favicon functionality on all WebView instances in this process.
+ *
+ * @deprecated This class is only required when running on devices
+ * up to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}
*/
+@Deprecated
public class WebIconDatabase {
/**
* Interface for receiving icons from the database.
+ * @deprecated This interface is obsolete.
*/
+ @Deprecated
public interface IconListener {
/**
* Called when the icon has been retrieved from the database and the
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index aa68904..8ae0021 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -37,6 +37,10 @@ public abstract class WebSettings {
* <li>SINGLE_COLUMN moves all content into one column that is the width of the
* view.</li>
* <li>NARROW_COLUMNS makes all columns no wider than the screen if possible.</li>
+ * <li>TEXT_AUTOSIZING boosts font size of paragraphs based on heuristics to make
+ * the text readable when viewing a wide-viewport layout in the overview mode.
+ * It is recommended to enable zoom support {@link #setSupportZoom} when
+ * using this mode.</li>
* </ul>
*/
// XXX: These must match LayoutAlgorithm in Settings.h in WebCore.
@@ -47,7 +51,11 @@ public abstract class WebSettings {
*/
@Deprecated
SINGLE_COLUMN,
- NARROW_COLUMNS
+ NARROW_COLUMNS,
+ /**
+ * @hide
+ */
+ TEXT_AUTOSIZING
}
/**
@@ -89,6 +97,14 @@ public abstract class WebSettings {
ZoomDensity(int size) {
value = size;
}
+
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ public int getValue() {
+ return value;
+ }
+
int value;
}
@@ -388,16 +404,14 @@ public abstract class WebSettings {
}
/**
- * Sets whether the WebView should save form data. The default is true,
- * unless in private browsing mode, when the value is always false.
+ * Sets whether the WebView should save form data. The default is true.
*/
public void setSaveFormData(boolean save) {
throw new MustOverrideException();
}
/**
- * Gets whether the WebView saves form data. Always false in private
- * browsing mode.
+ * Gets whether the WebView saves form data.
*
* @return whether the WebView saves form data
* @see #setSaveFormData
@@ -408,7 +422,9 @@ public abstract class WebSettings {
/**
* Sets whether the WebView should save passwords. The default is true.
+ * @deprecated Saving passwords in WebView will not be supported in future versions.
*/
+ @Deprecated
public void setSavePassword(boolean save) {
throw new MustOverrideException();
}
@@ -418,7 +434,9 @@ public abstract class WebSettings {
*
* @return whether the WebView saves passwords
* @see #setSavePassword
+ * @deprecated Saving passwords in WebView will not be supported in future versions.
*/
+ @Deprecated
public boolean getSavePassword() {
throw new MustOverrideException();
}
@@ -501,18 +519,20 @@ public abstract class WebSettings {
/**
* Enables using light touches to make a selection and activate mouseovers.
- * The default is false.
+ * @deprecated From {@link android.os.Build.VERSION_CODES#JELLY_BEAN} this
+ * setting is obsolete and has no effect.
*/
+ @Deprecated
public void setLightTouchEnabled(boolean enabled) {
throw new MustOverrideException();
}
/**
* Gets whether light touches are enabled.
- *
- * @return whether light touches are enabled
* @see #setLightTouchEnabled
+ * @deprecated This setting is obsolete.
*/
+ @Deprecated
public boolean getLightTouchEnabled() {
throw new MustOverrideException();
}
@@ -580,18 +600,25 @@ public abstract class WebSettings {
}
/**
- * Tells the WebView to use a wide viewport. The default is false.
+ * Sets whether the WebView should enable support for the &quot;viewport&quot;
+ * HTML meta tag or should use a wide viewport.
+ * When the value of the setting is false, the layout width is always set to the
+ * width of the WebView control in device-independent (CSS) pixels.
+ * When the value is true and the page contains the viewport meta tag, the value
+ * of the width specified in the tag is used. If the page does not contain the tag or
+ * does not provide a width, then a wide viewport will be used.
*
- * @param use whether to use a wide viewport
+ * @param use whether to enable support for the viewport meta tag
*/
public synchronized void setUseWideViewPort(boolean use) {
throw new MustOverrideException();
}
/**
- * Gets whether the WebView is using a wide viewport.
+ * Gets whether the WebView supports the &quot;viewport&quot;
+ * HTML meta tag or will use a wide viewport.
*
- * @return true if the WebView is using a wide viewport
+ * @return true if the WebView supports the viewport meta tag
* @see #setUseWideViewPort
*/
public synchronized boolean getUseWideViewPort() {
@@ -936,6 +963,9 @@ public abstract class WebSettings {
* access to content from other file scheme URLs. See
* {@link #setAllowFileAccessFromFileURLs}. To enable the most restrictive,
* and therefore secure policy, this setting should be disabled.
+ * Note that this setting affects only JavaScript access to file scheme
+ * resources. Other access to such resources, for example, from image HTML
+ * elements, is unaffected.
* <p>
* The default value is true for API level
* {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below,
@@ -953,6 +983,9 @@ public abstract class WebSettings {
* enable the most restrictive, and therefore secure policy, this setting
* should be disabled. Note that the value of this setting is ignored if
* the value of {@link #getAllowUniversalAccessFromFileURLs} is true.
+ * Note too, that this setting affects only JavaScript access to file scheme
+ * resources. Other access to such resources, for example, from image HTML
+ * elements, is unaffected.
* <p>
* The default value is true for API level
* {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below,
@@ -971,6 +1004,7 @@ public abstract class WebSettings {
* @param flag true if plugins should be enabled
* @deprecated This method has been deprecated in favor of
* {@link #setPluginState}
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}
*/
@Deprecated
public synchronized void setPluginsEnabled(boolean flag) {
@@ -985,7 +1019,9 @@ public abstract class WebSettings {
* {@link PluginState#OFF}.
*
* @param state a PluginState value
+ * @deprecated Plugins will not be supported in future, and should not be used.
*/
+ @Deprecated
public synchronized void setPluginState(PluginState state) {
throw new MustOverrideException();
}
@@ -997,6 +1033,7 @@ public abstract class WebSettings {
* @param pluginsPath a String path to the directory containing plugins
* @deprecated This method is no longer used as plugins are loaded from
* their own APK via the system's package manager.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}
*/
@Deprecated
public synchronized void setPluginsPath(String pluginsPath) {
@@ -1052,7 +1089,7 @@ public abstract class WebSettings {
*
* @param appCachePath a String path to the directory containing
* Application Caches files.
- * @see setAppCacheEnabled
+ * @see #setAppCacheEnabled
*/
public synchronized void setAppCachePath(String appCachePath) {
throw new MustOverrideException();
@@ -1064,9 +1101,12 @@ public abstract class WebSettings {
* 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}.
+ * It is recommended to leave the maximum size set to the default value.
*
* @param appCacheMaxSize the maximum size in bytes
+ * @deprecated In future quota will be managed automatically.
*/
+ @Deprecated
public synchronized void setAppCacheMaxSize(long appCacheMaxSize) {
throw new MustOverrideException();
}
@@ -1076,6 +1116,11 @@ public abstract class WebSettings {
* false. See also {@link #setDatabasePath} for how to correctly set up the
* database storage API.
*
+ * This setting is global in effect, across all WebView instances in a process.
+ * Note you should only modify this setting prior to making <b>any</b> WebView
+ * page load within a given process, as the WebView implementation may ignore
+ * changes to this setting after that point.
+ *
* @param flag true if the WebView should use the database storage API
*/
public synchronized void setDatabaseEnabled(boolean flag) {
@@ -1121,9 +1166,22 @@ public abstract class WebSettings {
}
/**
- * Sets whether Geolocation is enabled. The default is true. See also
- * {@link #setGeolocationDatabasePath} for how to correctly set up
- * Geolocation.
+ * Sets whether Geolocation is enabled. The default is true.
+ * <p>
+ * Please note that in order for the Geolocation API to be usable
+ * by a page in the WebView, the following requirements must be met:
+ * <ul>
+ * <li>an application must have permission to access the device location,
+ * see {@link android.Manifest.permission#ACCESS_COARSE_LOCATION},
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION};
+ * <li>an application must provide an implementation of the
+ * {@link WebChromeClient#onGeolocationPermissionsShowPrompt} callback
+ * to receive notifications that a page is requesting access to location
+ * via the JavaScript Geolocation API.
+ * </ul>
+ * <p>
+ * As an option, it is possible to store previous locations and web origin
+ * permissions in a database. See {@link #setGeolocationDatabasePath}.
*
* @param flag whether Geolocation should be enabled
*/
@@ -1168,6 +1226,7 @@ public abstract class WebSettings {
* @return true if plugins are enabled
* @see #setPluginsEnabled
* @deprecated This method has been replaced by {@link #getPluginState}
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}
*/
@Deprecated
public synchronized boolean getPluginsEnabled() {
@@ -1179,7 +1238,9 @@ public abstract class WebSettings {
*
* @return the plugin state as a {@link PluginState} value
* @see #setPluginState
+ * @deprecated Plugins will not be supported in future, and should not be used.
*/
+ @Deprecated
public synchronized PluginState getPluginState() {
throw new MustOverrideException();
}
@@ -1191,6 +1252,7 @@ public abstract class WebSettings {
* @return an empty string
* @deprecated This method is no longer used as plugins are loaded from
* their own APK via the system's package manager.
+ * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}
*/
@Deprecated
public synchronized String getPluginsPath() {
@@ -1284,7 +1346,10 @@ public abstract class WebSettings {
* {@link RenderPriority#NORMAL}.
*
* @param priority the priority
+ * @deprecated It is not recommended to adjust thread priorities, and this will
+ * not be supported in future versions.
*/
+ @Deprecated
public synchronized void setRenderPriority(RenderPriority priority) {
throw new MustOverrideException();
}
@@ -1295,7 +1360,7 @@ public abstract class WebSettings {
* and content is re-validated as needed. When navigating back, content is
* 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},
+ * one of {@link #LOAD_DEFAULT},
* {@link #LOAD_CACHE_ELSE_NETWORK}, {@link #LOAD_NO_CACHE} or
* {@link #LOAD_CACHE_ONLY}. The default value is {@link #LOAD_DEFAULT}.
*
diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java
index 1bbe7bb..c10a429 100644
--- a/core/java/android/webkit/WebSettingsClassic.java
+++ b/core/java/android/webkit/WebSettingsClassic.java
@@ -647,10 +647,6 @@ public class WebSettingsClassic extends WebSettings {
@Override
public synchronized void setTextZoom(int textZoom) {
if (mTextSize != textZoom) {
- if (WebViewClassic.mLogEvent) {
- EventLog.writeEvent(EventLogTags.BROWSER_TEXT_SIZE_CHANGE,
- mTextSize, textZoom);
- }
mTextSize = textZoom;
postSync();
}
@@ -820,6 +816,10 @@ public class WebSettingsClassic extends WebSettings {
*/
@Override
public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) {
+ if (l == LayoutAlgorithm.TEXT_AUTOSIZING) {
+ throw new IllegalArgumentException(
+ "WebViewClassic does not support TEXT_AUTOSIZING layout mode");
+ }
// XXX: This will only be affective if libwebcore was built with
// ANDROID_LAYOUT defined.
if (mLayoutAlgorithm != l) {
diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java
index 1e955bd..7d9373c 100644
--- a/core/java/android/webkit/WebStorage.java
+++ b/core/java/android/webkit/WebStorage.java
@@ -171,7 +171,9 @@ public class WebStorage {
* The quota is specified in bytes and the origin is specified using its string
* representation. Note that a quota is not enforced on a per-origin basis
* for the Application Cache API.
+ * @deprecated Controlling quota per-origin will not be supported in future.
*/
+ @Deprecated
public void setQuotaForOrigin(String origin, long quota) {
// Must be a no-op for backward compatibility: see the hidden constructor for reason.
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 9feb513..afa4894 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -26,6 +26,7 @@ import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
+import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.os.Message;
@@ -208,8 +209,7 @@ import java.util.Map;
* and default scaling is not applied to the web page; if the value is "1.5", then the device is
* considered a high density device (hdpi) and the page content is scaled 1.5x; if the
* value is "0.75", then the device is considered a low density device (ldpi) and the content is
- * scaled 0.75x. However, if you specify the {@code "target-densitydpi"} meta property
- * (discussed below), then you can stop this default scaling behavior.</li>
+ * scaled 0.75x.</li>
* <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen
* densities for which this style sheet is to be used. The corresponding value should be either
* "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium
@@ -219,29 +219,6 @@ import java.util.Map;
* <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5,
* which is the high density pixel ratio.</p>
* </li>
- * <li>The {@code target-densitydpi} property for the {@code viewport} meta tag. You can use
- * this to specify the target density for which the web page is designed, using the following
- * values:
- * <ul>
- * <li>{@code device-dpi} - Use the device's native dpi as the target dpi. Default scaling never
- * occurs.</li>
- * <li>{@code high-dpi} - Use hdpi as the target dpi. Medium and low density screens scale down
- * as appropriate.</li>
- * <li>{@code medium-dpi} - Use mdpi as the target dpi. High density screens scale up and
- * low density screens scale down. This is also the default behavior.</li>
- * <li>{@code low-dpi} - Use ldpi as the target dpi. Medium and high density screens scale up
- * as appropriate.</li>
- * <li><em>{@code <value>}</em> - Specify a dpi value to use as the target dpi (accepted
- * values are 70-400).</li>
- * </ul>
- * <p>Here's an example meta tag to specify the target density:</p>
- * <pre>&lt;meta name="viewport" content="target-densitydpi=device-dpi" /&gt;</pre></li>
- * </ul>
- * <p>If you want to modify your web page for different densities, by using the {@code
- * -webkit-device-pixel-ratio} CSS media query and/or the {@code
- * window.devicePixelRatio} DOM property, then you should set the {@code target-densitydpi} meta
- * property to {@code device-dpi}. This stops Android from performing scaling in your web page and
- * allows you to make the necessary adjustments for each density via CSS and JavaScript.</p>
*
* <h3>HTML5 Video support</h3>
*
@@ -265,6 +242,11 @@ public class WebView extends AbsoluteLayout
private static final String LOGTAG = "webview_proxy";
+ // Throwing an exception for incorrect thread usage if the
+ // build target is JB MR2 or newer. Defaults to false, and is
+ // set in the WebView constructor.
+ private static Boolean sEnforceThreadChecking = false;
+
/**
* Transportation object for returning WebView across thread boundaries.
*/
@@ -334,7 +316,9 @@ public class WebView extends AbsoluteLayout
* See {@link WebView#capturePicture} for details of the picture.
*
* @param view the WebView that owns the picture
- * @param picture the new picture
+ * @param picture the new picture. Applications targeting
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} or above
+ * will always receive a null Picture.
* @deprecated Deprecated due to internal changes.
*/
@Deprecated
@@ -505,6 +489,8 @@ public class WebView extends AbsoluteLayout
if (context == null) {
throw new IllegalArgumentException("Invalid context argument");
}
+ sEnforceThreadChecking = context.getApplicationInfo().targetSdkVersion >=
+ Build.VERSION_CODES.JELLY_BEAN_MR2;
checkThread();
ensureProviderCreated();
@@ -601,7 +587,9 @@ public class WebView extends AbsoluteLayout
* @param password the password for the given host
* @see WebViewDatabase#clearUsernamePassword
* @see WebViewDatabase#hasUsernamePassword
+ * @deprecated Saving passwords in WebView will not be supported in future versions.
*/
+ @Deprecated
public void savePassword(String host, String username, String password) {
checkThread();
mProvider.savePassword(host, username, password);
@@ -616,7 +604,7 @@ public class WebView extends AbsoluteLayout
* @param realm the realm to which the credentials apply
* @param username the username
* @param password the password
- * @see getHttpAuthUsernamePassword
+ * @see #getHttpAuthUsernamePassword
* @see WebViewDatabase#hasHttpAuthUsernamePassword
* @see WebViewDatabase#clearHttpAuthUsernamePassword
*/
@@ -636,7 +624,7 @@ public class WebView extends AbsoluteLayout
* @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 #setHttpAuthUsernamePassword
* @see WebViewDatabase#hasHttpAuthUsernamePassword
* @see WebViewDatabase#clearHttpAuthUsernamePassword
*/
@@ -853,7 +841,7 @@ public class WebView extends AbsoluteLayout
* defaults to 'text/html'.
* @param encoding the encoding of the data
* @param historyUrl the URL to use as the history entry. If null defaults
- * to 'about:blank'.
+ * to 'about:blank'. If non-null, this must be a valid URL.
*/
public void loadDataWithBaseURL(String baseUrl, String data,
String mimeType, String encoding, String historyUrl) {
@@ -998,7 +986,10 @@ public class WebView extends AbsoluteLayout
/**
* Clears this WebView so that onDraw() will draw nothing but white background,
* and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY.
+ * @deprecated Use WebView.loadUrl("about:blank") to reliably reset the view state
+ * and release page resources (including any running JavaScript).
*/
+ @Deprecated
public void clearView() {
checkThread();
mProvider.clearView();
@@ -1330,7 +1321,8 @@ public class WebView extends AbsoluteLayout
*/
public void setFindListener(FindListener listener) {
checkThread();
- mProvider.setFindListener(listener);
+ setupFindListenerIfNeeded();
+ mFindListener.mUserFindListener = listener;
}
/**
@@ -1387,7 +1379,11 @@ public class WebView extends AbsoluteLayout
* @param showIme if true, show the IME, assuming the user will begin typing.
* If false and text is non-null, perform a find all.
* @return true if the find dialog is shown, false otherwise
+ * @deprecated This method does not work reliably on all Android versions;
+ * implementing a custom find dialog using WebView.findAllAsync()
+ * provides a more robust solution.
*/
+ @Deprecated
public boolean showFindDialog(String text, boolean showIme) {
checkThread();
return mProvider.showFindDialog(text, showIme);
@@ -1851,11 +1847,60 @@ public class WebView extends AbsoluteLayout
}
//-------------------------------------------------------------------------
+ // Package-private internal stuff
+ //-------------------------------------------------------------------------
+
+ // Only used by android.webkit.FindActionModeCallback.
+ void setFindDialogFindListener(FindListener listener) {
+ checkThread();
+ setupFindListenerIfNeeded();
+ mFindListener.mFindDialogFindListener = listener;
+ }
+
+ // Only used by android.webkit.FindActionModeCallback.
+ void notifyFindDialogDismissed() {
+ checkThread();
+ mProvider.notifyFindDialogDismissed();
+ }
+
+ //-------------------------------------------------------------------------
// Private internal stuff
//-------------------------------------------------------------------------
private WebViewProvider mProvider;
+ /**
+ * In addition to the FindListener that the user may set via the WebView.setFindListener
+ * API, FindActionModeCallback will register it's own FindListener. We keep them separate
+ * via this class so that that the two FindListeners can potentially exist at once.
+ */
+ private class FindListenerDistributor implements FindListener {
+ private FindListener mFindDialogFindListener;
+ private FindListener mUserFindListener;
+
+ @Override
+ public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
+ boolean isDoneCounting) {
+ if (mFindDialogFindListener != null) {
+ mFindDialogFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
+ isDoneCounting);
+ }
+
+ if (mUserFindListener != null) {
+ mUserFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
+ isDoneCounting);
+ }
+ }
+ }
+ private FindListenerDistributor mFindListener;
+
+ private void setupFindListenerIfNeeded() {
+ if (mFindListener == null) {
+ mFindListener = new FindListenerDistributor();
+ mProvider.setFindListener(mFindListener);
+ }
+ }
+
private void ensureProviderCreated() {
checkThread();
if (mProvider == null) {
@@ -1866,9 +1911,6 @@ public class WebView extends AbsoluteLayout
}
private static synchronized WebViewFactoryProvider getFactory() {
- // For now the main purpose of this function (and the factory abstration) is to keep
- // us honest and minimize usage of WebViewClassic internals when binding the proxy.
- checkThread();
return WebViewFactory.getProvider();
}
@@ -1881,6 +1923,10 @@ public class WebView extends AbsoluteLayout
"Future versions of WebView may not support use on other threads.");
Log.w(LOGTAG, Log.getStackTraceString(throwable));
StrictMode.onWebViewMethodCalledOnWrongThread(throwable);
+
+ if (sEnforceThreadChecking) {
+ throw new RuntimeException(throwable);
+ }
}
}
@@ -1911,9 +1957,8 @@ public class WebView extends AbsoluteLayout
@Override
public void setOverScrollMode(int mode) {
super.setOverScrollMode(mode);
- // This method may called in the constructor chain, before the WebView provider is
- // created. (Fortunately, this is the only method we override that can get called by
- // any of the base class constructors).
+ // This method may be called in the constructor chain, before the WebView provider is
+ // created.
ensureProviderCreated();
mProvider.getViewDelegate().setOverScrollMode(mode);
}
@@ -2073,6 +2118,9 @@ public class WebView extends AbsoluteLayout
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
+ // This method may be called in the constructor chain, before the WebView provider is
+ // created.
+ ensureProviderCreated();
mProvider.getViewDelegate().onVisibilityChanged(changedView, visibility);
}
@@ -2138,4 +2186,10 @@ public class WebView extends AbsoluteLayout
super.setLayerType(layerType, paint);
mProvider.getViewDelegate().setLayerType(layerType, paint);
}
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ mProvider.getViewDelegate().preDispatchDraw(canvas);
+ super.dispatchDraw(canvas);
+ }
}
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 4db9f6c..a324502 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -1024,30 +1024,26 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
static final int UPDATE_MATCH_COUNT = 126;
static final int CENTER_FIT_RECT = 127;
static final int SET_SCROLLBAR_MODES = 129;
- static final int SELECTION_STRING_CHANGED = 130;
- static final int HIT_TEST_RESULT = 131;
- static final int SAVE_WEBARCHIVE_FINISHED = 132;
-
- static final int SET_AUTOFILLABLE = 133;
- static final int AUTOFILL_COMPLETE = 134;
-
- static final int SCREEN_ON = 136;
- static final int UPDATE_ZOOM_DENSITY = 139;
- static final int EXIT_FULLSCREEN_VIDEO = 140;
-
- static final int COPY_TO_CLIPBOARD = 141;
- static final int INIT_EDIT_FIELD = 142;
- static final int REPLACE_TEXT = 143;
- static final int CLEAR_CARET_HANDLE = 144;
- static final int KEY_PRESS = 145;
- static final int RELOCATE_AUTO_COMPLETE_POPUP = 146;
- static final int FOCUS_NODE_CHANGED = 147;
- static final int AUTOFILL_FORM = 148;
- static final int SCROLL_EDIT_TEXT = 149;
- 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;
+ static final int HIT_TEST_RESULT = 130;
+ static final int SAVE_WEBARCHIVE_FINISHED = 131;
+ static final int SET_AUTOFILLABLE = 132;
+ static final int AUTOFILL_COMPLETE = 133;
+ static final int SCREEN_ON = 134;
+ static final int UPDATE_ZOOM_DENSITY = 135;
+ static final int EXIT_FULLSCREEN_VIDEO = 136;
+ static final int COPY_TO_CLIPBOARD = 137;
+ static final int INIT_EDIT_FIELD = 138;
+ static final int REPLACE_TEXT = 139;
+ static final int CLEAR_CARET_HANDLE = 140;
+ static final int KEY_PRESS = 141;
+ static final int RELOCATE_AUTO_COMPLETE_POPUP = 142;
+ static final int FOCUS_NODE_CHANGED = 143;
+ static final int AUTOFILL_FORM = 144;
+ static final int SCROLL_EDIT_TEXT = 145;
+ static final int EDIT_TEXT_SIZE_CHANGED = 146;
+ static final int SHOW_CARET_HANDLE = 147;
+ static final int UPDATE_CONTENT_BOUNDS = 148;
+ static final int SCROLL_HANDLE_INTO_VIEW = 149;
private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
@@ -1800,6 +1796,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0));
}
+ /* package */ void handleSelectionChangedWebCoreThread(String selection, int token) {
+ if (isAccessibilityInjectionEnabled()) {
+ getAccessibilityInjector().onSelectionStringChangedWebCoreThread(selection, token);
+ }
+ }
+
private boolean isAccessibilityInjectionEnabled() {
final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
if (!manager.isEnabled()) {
@@ -2147,7 +2149,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
@Override
public void destroy() {
if (mWebView.getViewRootImpl() != null) {
- Log.e(LOGTAG, "Error: WebView.destroy() called while still attached!");
+ Log.e(LOGTAG, Log.getStackTraceString(
+ new Throwable("Error: WebView.destroy() called while still attached!")));
}
ensureFunctorDetached();
destroyJava();
@@ -3702,7 +3705,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mCachedOverlappingActionModeHeight = -1;
mFindCallback = callback;
setFindIsUp(true);
- mFindCallback.setWebView(this);
+ mFindCallback.setWebView(getWebView());
if (showIme) {
mFindCallback.showSoftInput();
} else if (text != null) {
@@ -3804,7 +3807,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
/**
* Called when the find ActionMode ends.
*/
- void notifyFindDialogDismissed() {
+ @Override
+ public void notifyFindDialogDismissed() {
mFindCallback = null;
mCachedOverlappingActionModeHeight = -1;
if (mWebViewCore == null) {
@@ -7497,13 +7501,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mVerticalScrollBarMode = msg.arg2;
break;
- case SELECTION_STRING_CHANGED:
- if (isAccessibilityInjectionEnabled()) {
- getAccessibilityInjector()
- .handleSelectionChangedIfNecessary((String) msg.obj);
- }
- break;
-
case FOCUS_NODE_CHANGED:
mIsEditingText = (msg.arg1 == mFieldPointer);
if (mAutoCompletePopup != null && !mIsEditingText) {
@@ -7913,7 +7910,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (mPictureListener != null) {
// trigger picture listener for hardware layers. Software layers are
// triggered in setNewPicture
- mPictureListener.onNewPicture(getWebView(), capturePicture());
+ Picture picture = mContext.getApplicationInfo().targetSdkVersion <
+ Build.VERSION_CODES.JELLY_BEAN_MR2 ? capturePicture() : null;
+ mPictureListener.onNewPicture(getWebView(), picture);
}
}
@@ -7998,7 +7997,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|| mWebView.getLayerType() == View.LAYER_TYPE_SOFTWARE) {
// trigger picture listener for software layers. Hardware layers are
// triggered in pageSwapCallback
- mPictureListener.onNewPicture(getWebView(), capturePicture());
+ Picture picture = mContext.getApplicationInfo().targetSdkVersion <
+ Build.VERSION_CODES.JELLY_BEAN_MR2 ? capturePicture() : null;
+ mPictureListener.onNewPicture(getWebView(), picture);
}
}
}
@@ -8564,6 +8565,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
updateHwAccelerated();
}
+ @Override
+ public void preDispatchDraw(Canvas canvas) {
+ // no-op for WebViewClassic.
+ }
+
private void updateHwAccelerated() {
if (mNativeClass == 0) {
return;
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 08a046a..e8974c6 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -31,6 +31,7 @@ public class WebViewClient {
* proper handler for the url. If WebViewClient is provided, return true
* means the host application handles the url, while return false means the
* current WebView handles the url.
+ * This method is not called for requests using the POST "method".
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
@@ -82,9 +83,9 @@ public class WebViewClient {
* Notify the host application of a resource request and allow the
* application to return the data. If the return value is null, the WebView
* will continue to load the resource as usual. Otherwise, the return
- * response and data will be used. NOTE: This method is called by the
- * network thread so clients should exercise caution when accessing private
- * data.
+ * response and data will be used. NOTE: This method is called on a thread
+ * other than the UI thread so clients should exercise caution
+ * when accessing private data or the view system.
*
* @param view The {@link android.webkit.WebView} that is requesting the
* resource.
@@ -213,7 +214,7 @@ public class WebViewClient {
* @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
+ * @see WebView#getHttpAuthUsernamePassword
*/
public void onReceivedHttpAuthRequest(WebView view,
HttpAuthHandler handler, String host, String realm) {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index c35b768..4a09636 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -41,6 +41,8 @@ import android.view.View;
import android.webkit.WebViewClassic.FocusNodeHref;
import android.webkit.WebViewInputDispatcher.WebKitCallbacks;
+import com.android.internal.os.SomeArgs;
+
import junit.framework.Assert;
import java.io.OutputStream;
@@ -1545,12 +1547,14 @@ public final class WebViewCore {
case MODIFY_SELECTION:
mTextSelectionChangeReason
= TextSelectionData.REASON_ACCESSIBILITY_INJECTOR;
- String modifiedSelectionString =
- nativeModifySelection(mNativeClass, msg.arg1,
- msg.arg2);
- mWebViewClassic.mPrivateHandler.obtainMessage(
- WebViewClassic.SELECTION_STRING_CHANGED,
- modifiedSelectionString).sendToTarget();
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final String modifiedSelectionString = nativeModifySelection(
+ mNativeClass, args.argi1, args.argi2);
+ // If accessibility is on, the main thread may be
+ // waiting for a response. Send on webcore thread.
+ mWebViewClassic.handleSelectionChangedWebCoreThread(
+ modifiedSelectionString, args.argi3);
+ args.recycle();
mTextSelectionChangeReason
= TextSelectionData.REASON_UNKNOWN;
break;
@@ -2001,9 +2005,6 @@ public final class WebViewCore {
private void clearCache(boolean includeDiskFiles) {
mBrowserFrame.clearCache();
- if (includeDiskFiles) {
- CacheManager.removeAllCacheFiles();
- }
}
private void loadUrl(String url, Map<String, String> extraHeaders) {
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index 5597259..99e0ffb 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -50,8 +50,10 @@ public class WebViewDatabase {
*
* @return true if there are any saved username/password pairs
* @see WebView#savePassword
- * @see clearUsernamePassword
+ * @see #clearUsernamePassworda
+ * @deprecated Saving passwords in WebView will not be supported in future versions.
*/
+ @Deprecated
public boolean hasUsernamePassword() {
throw new MustOverrideException();
}
@@ -61,8 +63,10 @@ public class WebViewDatabase {
* Note that these are unrelated to HTTP authentication credentials.
*
* @see WebView#savePassword
- * @see hasUsernamePassword
+ * @see #hasUsernamePassword
+ * @deprecated Saving passwords in WebView will not be supported in future versions.
*/
+ @Deprecated
public void clearUsernamePassword() {
throw new MustOverrideException();
}
@@ -71,9 +75,9 @@ public class WebViewDatabase {
* Gets whether there are any saved credentials for HTTP authentication.
*
* @return whether there are any saved credentials
- * @see Webview#getHttpAuthUsernamePassword
- * @see Webview#setHttpAuthUsernamePassword
- * @see clearHttpAuthUsernamePassword
+ * @see WebView#getHttpAuthUsernamePassword
+ * @see WebView#setHttpAuthUsernamePassword
+ * @see #clearHttpAuthUsernamePassword
*/
public boolean hasHttpAuthUsernamePassword() {
throw new MustOverrideException();
@@ -82,9 +86,9 @@ public class WebViewDatabase {
/**
* Clears any saved credentials for HTTP authentication.
*
- * @see Webview#getHttpAuthUsernamePassword
- * @see Webview#setHttpAuthUsernamePassword
- * @see hasHttpAuthUsernamePassword
+ * @see WebView#getHttpAuthUsernamePassword
+ * @see WebView#setHttpAuthUsernamePassword
+ * @see #hasHttpAuthUsernamePassword
*/
public void clearHttpAuthUsernamePassword() {
throw new MustOverrideException();
@@ -94,7 +98,7 @@ public class WebViewDatabase {
* Gets whether there is any saved data for web forms.
*
* @return whether there is any saved data for web forms
- * @see clearFormData
+ * @see #clearFormData
*/
public boolean hasFormData() {
throw new MustOverrideException();
@@ -103,7 +107,7 @@ public class WebViewDatabase {
/**
* Clears any saved data for web forms.
*
- * @see hasFormData
+ * @see #hasFormData
*/
public void clearFormData() {
throw new MustOverrideException();
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index b833a01..00d87bd 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -25,13 +25,18 @@ import dalvik.system.PathClassLoader;
/**
* Top level factory, used creating all the main WebView implementation classes.
+ *
+ * @hide
*/
-class WebViewFactory {
+public final class WebViewFactory {
+ public static final String WEBVIEW_EXPERIMENTAL_PROPERTY = "persist.sys.webview.exp";
+ private static final String DEPRECATED_CHROMIUM_PROPERTY = "webview.use_chromium";
+
// Default Provider factory class name.
// 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";
+ "com.android.webview.chromium.WebViewChromiumFactoryProvider";
private static final String CHROMIUM_WEBVIEW_JAR = "/system/framework/webviewchromium.jar";
private static final String LOGTAG = "WebViewFactory";
@@ -43,16 +48,17 @@ class WebViewFactory {
private static WebViewFactoryProvider sProviderInstance;
private static final Object sProviderLock = new Object();
+ public static boolean isExperimentalWebViewAvailable() {
+ return Build.IS_DEBUGGABLE && (new java.io.File(CHROMIUM_WEBVIEW_JAR).exists());
+ }
+
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;
- // 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)) {
+ if (isExperimentalWebViewEnabled()) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
sProviderInstance = loadChromiumProvider();
@@ -76,6 +82,20 @@ class WebViewFactory {
}
}
+ // For debug builds, we allow a system property to specify that we should use the
+ // experimtanl Chromium powered WebView. This enables us to switch between
+ // implementations at runtime. For user (release) builds, don't allow this.
+ private static boolean isExperimentalWebViewEnabled() {
+ if (!isExperimentalWebViewAvailable())
+ return false;
+ if (SystemProperties.getBoolean(DEPRECATED_CHROMIUM_PROPERTY, false)) {
+ Log.w(LOGTAG, String.format("The property %s has been deprecated. Please use %s.",
+ DEPRECATED_CHROMIUM_PROPERTY, WEBVIEW_EXPERIMENTAL_PROPERTY));
+ return true;
+ }
+ return SystemProperties.getBoolean(WEBVIEW_EXPERIMENTAL_PROPERTY, false);
+ }
+
// 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() {
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index c9f9fbd..fa17ab9 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -240,7 +240,7 @@ public interface WebViewProvider {
public View findHierarchyView(String className, int hashCode);
//-------------------------------------------------------------------------
- // Provider glue methods
+ // Provider internal methods
//-------------------------------------------------------------------------
/**
@@ -255,6 +255,12 @@ public interface WebViewProvider {
*/
/* package */ ScrollDelegate getScrollDelegate();
+ /**
+ * Only used by FindActionModeCallback to inform providers that the find dialog has
+ * been dismissed.
+ */
+ public void notifyFindDialogDismissed();
+
//-------------------------------------------------------------------------
// View / ViewGroup delegation methods
//-------------------------------------------------------------------------
@@ -341,6 +347,8 @@ public interface WebViewProvider {
public void setBackgroundColor(int color);
public void setLayerType(int layerType, Paint paint);
+
+ public void preDispatchDraw(Canvas canvas);
}
interface ScrollDelegate {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 57bf0d3..219891c 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1374,9 +1374,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (isEnabled()) {
if (getFirstVisiblePosition() > 0) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ info.setScrollable(true);
}
if (getLastVisiblePosition() < getCount() - 1) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.setScrollable(true);
}
}
}
@@ -1405,6 +1407,22 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return false;
}
+ /** @hide */
+ @Override
+ public View findViewByAccessibilityIdTraversal(int accessibilityId) {
+ if (accessibilityId == getAccessibilityViewId()) {
+ return this;
+ }
+ // If the data changed the children are invalid since the data model changed.
+ // Hence, we pretend they do not exist. After a layout the children will sync
+ // with the model at which point we notify that the accessibility state changed,
+ // so a service will be able to re-fetch the views.
+ if (mDataChanged) {
+ return null;
+ }
+ return super.findViewByAccessibilityIdTraversal(accessibilityId);
+ }
+
/**
* Indicates whether the children's drawing cache is used during a scroll.
* By default, the drawing cache is enabled but this will consume more memory.
@@ -2195,6 +2213,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
class ListItemAccessibilityDelegate extends AccessibilityDelegate {
@Override
+ public AccessibilityNodeInfo createAccessibilityNodeInfo(View host) {
+ // If the data changed the children are invalid since the data model changed.
+ // Hence, we pretend they do not exist. After a layout the children will sync
+ // with the model at which point we notify that the accessibility state changed,
+ // so a service will be able to re-fetch the views.
+ if (mDataChanged) {
+ return null;
+ }
+ return super.createAccessibilityNodeInfo(host);
+ }
+
+ @Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
@@ -2604,7 +2634,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mGlobalLayoutListenerAddedFilter = false;
}
- if (mAdapter != null) {
+ if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
mDataSetObserver = null;
}
@@ -2697,6 +2727,15 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mLastTouchMode = touchMode;
}
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+
+ if (mFastScroller != null) {
+ mFastScroller.setScrollbarPosition(getVerticalScrollbarPosition());
+ }
+ }
+
/**
* Creates the ContextMenuInfo returned from {@link #getContextMenuInfo()}. This
* methods knows the view, position and ID of the item that received the
@@ -6165,6 +6204,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private ArrayList<View> mSkippedScrap;
private SparseArray<View> mTransientStateViews;
+ private LongSparseArray<View> mTransientStateViewsById;
public void setViewTypeCount(int viewTypeCount) {
if (viewTypeCount < 1) {
@@ -6203,6 +6243,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mTransientStateViews.valueAt(i).forceLayout();
}
}
+ if (mTransientStateViewsById != null) {
+ final int count = mTransientStateViewsById.size();
+ for (int i = 0; i < count; i++) {
+ mTransientStateViewsById.valueAt(i).forceLayout();
+ }
+ }
}
public boolean shouldRecycleViewType(int viewType) {
@@ -6232,6 +6278,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mTransientStateViews != null) {
mTransientStateViews.clear();
}
+ if (mTransientStateViewsById != null) {
+ mTransientStateViewsById.clear();
+ }
}
/**
@@ -6279,16 +6328,21 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
View getTransientStateView(int position) {
- if (mTransientStateViews == null) {
- return null;
+ if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) {
+ long id = mAdapter.getItemId(position);
+ View result = mTransientStateViewsById.get(id);
+ mTransientStateViewsById.remove(id);
+ return result;
}
- final int index = mTransientStateViews.indexOfKey(position);
- if (index < 0) {
- return null;
+ if (mTransientStateViews != null) {
+ final int index = mTransientStateViews.indexOfKey(position);
+ if (index >= 0) {
+ View result = mTransientStateViews.valueAt(index);
+ mTransientStateViews.removeAt(index);
+ return result;
+ }
}
- final View result = mTransientStateViews.valueAt(index);
- mTransientStateViews.removeAt(index);
- return result;
+ return null;
}
/**
@@ -6298,6 +6352,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (mTransientStateViews != null) {
mTransientStateViews.clear();
}
+ if (mTransientStateViewsById != null) {
+ mTransientStateViewsById.clear();
+ }
}
/**
@@ -6333,18 +6390,27 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
int viewType = lp.viewType;
final boolean scrapHasTransientState = scrap.hasTransientState();
if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
- if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || scrapHasTransientState) {
+ if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER && scrapHasTransientState) {
if (mSkippedScrap == null) {
mSkippedScrap = new ArrayList<View>();
}
mSkippedScrap.add(scrap);
}
if (scrapHasTransientState) {
- if (mTransientStateViews == null) {
- mTransientStateViews = new SparseArray<View>();
- }
scrap.dispatchStartTemporaryDetach();
- mTransientStateViews.put(position, scrap);
+ if (mAdapter != null && mAdapterHasStableIds) {
+ if (mTransientStateViewsById == null) {
+ mTransientStateViewsById = new LongSparseArray<View>();
+ }
+ mTransientStateViewsById.put(lp.itemId, scrap);
+ } else if (!mDataChanged) {
+ // avoid putting views on transient state list during a data change;
+ // the layout positions may be out of sync with the adapter positions
+ if (mTransientStateViews == null) {
+ mTransientStateViews = new SparseArray<View>();
+ }
+ mTransientStateViews.put(position, scrap);
+ }
}
return;
}
@@ -6398,15 +6464,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final boolean scrapHasTransientState = victim.hasTransientState();
if (!shouldRecycleViewType(whichScrap) || scrapHasTransientState) {
// Do not move views that should be ignored
- if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER ||
+ if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER &&
scrapHasTransientState) {
removeDetachedView(victim, false);
}
if (scrapHasTransientState) {
- if (mTransientStateViews == null) {
- mTransientStateViews = new SparseArray<View>();
+ if (mAdapter != null && mAdapterHasStableIds) {
+ if (mTransientStateViewsById == null) {
+ mTransientStateViewsById = new LongSparseArray<View>();
+ }
+ long id = mAdapter.getItemId(mFirstActivePosition + i);
+ mTransientStateViewsById.put(id, victim);
+ } else {
+ if (mTransientStateViews == null) {
+ mTransientStateViews = new SparseArray<View>();
+ }
+ mTransientStateViews.put(mFirstActivePosition + i, victim);
}
- mTransientStateViews.put(mFirstActivePosition + i, victim);
}
continue;
}
@@ -6455,6 +6529,15 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
}
+ if (mTransientStateViewsById != null) {
+ for (int i = 0; i < mTransientStateViewsById.size(); i++) {
+ final View v = mTransientStateViewsById.valueAt(i);
+ if (!v.hasTransientState()) {
+ mTransientStateViewsById.removeAt(i);
+ i--;
+ }
+ }
+ }
}
/**
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 3b5e75b..7674837 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -305,7 +305,7 @@ public abstract class AbsSeekBar extends ProgressBar {
}
// Canvas will be translated, so 0,0 is where we start drawing
- final int left = isLayoutRtl() ? available - thumbPos : thumbPos;
+ final int left = (isLayoutRtl() && mMirrorForRtl) ? available - thumbPos : thumbPos;
thumb.setBounds(left, topBound, left + thumbWidth, bottomBound);
}
@@ -426,7 +426,7 @@ public abstract class AbsSeekBar extends ProgressBar {
int x = (int)event.getX();
float scale;
float progress = 0;
- if (isLayoutRtl()) {
+ if (isLayoutRtl() && mMirrorForRtl) {
if (x > width - mPaddingRight) {
scale = 0.0f;
} else if (x < mPaddingLeft) {
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index f279f8e..f26527f 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -200,9 +200,7 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
if (view != null) {
// Put in recycler for re-measuring and/or layout
mRecycler.put(selectedPosition, view);
- }
- if (view != null) {
if (view.getLayoutParams() == null) {
mBlockLayoutRequests = true;
view.setLayoutParams(generateDefaultLayoutParams());
@@ -375,7 +373,7 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
/**
* Constructor called from {@link #CREATOR}
*/
- private SavedState(Parcel in) {
+ SavedState(Parcel in) {
super(in);
selectedId = in.readLong();
position = in.readInt();
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index 06dadb0..34cfea5 100644
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -20,6 +20,7 @@ import com.android.internal.R;
import android.app.AlertDialog;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -66,20 +67,18 @@ public class AppSecurityPermissions {
private final static String TAG = "AppSecurityPermissions";
private final static boolean localLOGV = false;
- private Context mContext;
- private LayoutInflater mInflater;
- private PackageManager mPm;
- private PackageInfo mInstalledPackageInfo;
+ private final Context mContext;
+ private final LayoutInflater mInflater;
+ private final PackageManager mPm;
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 final PermissionGroupInfoComparator mPermGroupComparator = new PermissionGroupInfoComparator();
+ private final PermissionInfoComparator mPermComparator = new PermissionInfoComparator();
+ private final List<MyPermissionInfo> mPermsList = new ArrayList<MyPermissionInfo>();
+ private final CharSequence mNewPermPrefix;
+ private String mPackageName;
static class MyPermissionGroupInfo extends PermissionGroupInfo {
CharSequence mLabel;
@@ -113,7 +112,7 @@ public class AppSecurityPermissions {
}
}
- static class MyPermissionInfo extends PermissionInfo {
+ private static class MyPermissionInfo extends PermissionInfo {
CharSequence mLabel;
/**
@@ -132,25 +131,17 @@ public class AppSecurityPermissions {
*/
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;
+ private boolean mShowRevokeUI = false;
+ private String mPackageName;
public PermissionItemView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -158,9 +149,12 @@ public class AppSecurityPermissions {
}
public void setPermission(MyPermissionGroupInfo grp, MyPermissionInfo perm,
- boolean first, CharSequence newPermPrefix) {
+ boolean first, CharSequence newPermPrefix, String packageName,
+ boolean showRevokeUI) {
mGroup = grp;
mPerm = perm;
+ mShowRevokeUI = showRevokeUI;
+ mPackageName = packageName;
ImageView permGrpIcon = (ImageView) findViewById(R.id.perm_icon);
TextView permNameView = (TextView) findViewById(R.id.perm_name);
@@ -219,6 +213,7 @@ public class AppSecurityPermissions {
}
builder.setCancelable(true);
builder.setIcon(mGroup.loadGroupIcon(pm));
+ addRevokeUIIfNecessary(builder);
mDialog = builder.show();
mDialog.setCanceledOnTouchOutside(true);
}
@@ -231,27 +226,43 @@ public class AppSecurityPermissions {
mDialog.dismiss();
}
}
+
+ private void addRevokeUIIfNecessary(AlertDialog.Builder builder) {
+ if (!mShowRevokeUI) {
+ return;
+ }
+
+ final boolean isRequired =
+ ((mPerm.mExistingReqFlags & PackageInfo.REQUESTED_PERMISSION_REQUIRED) != 0);
+
+ if (isRequired) {
+ return;
+ }
+
+ DialogInterface.OnClickListener ocl = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ PackageManager pm = getContext().getPackageManager();
+ pm.revokePermission(mPackageName, mPerm.name);
+ PermissionItemView.this.setVisibility(View.GONE);
+ }
+ };
+ builder.setNegativeButton(R.string.revoke, ocl);
+ builder.setPositiveButton(R.string.ok, null);
+ }
}
- public AppSecurityPermissions(Context context, List<PermissionInfo> permList) {
+ private AppSecurityPermissions(Context context) {
mContext = context;
+ mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mPm = mContext.getPackageManager();
- loadResources();
- mPermComparator = new PermissionInfoComparator();
- mPermGroupComparator = new PermissionGroupInfoComparator();
- for (PermissionInfo pi : permList) {
- mPermsList.add(new MyPermissionInfo(pi));
- }
- setPermissions(mPermsList);
+ // Pick up from framework resources instead.
+ mNewPermPrefix = mContext.getText(R.string.perms_new_perm_prefix);
}
-
+
public AppSecurityPermissions(Context context, String packageName) {
- mContext = context;
- mPm = mContext.getPackageManager();
- loadResources();
- mPermComparator = new PermissionInfoComparator();
- mPermGroupComparator = new PermissionGroupInfoComparator();
- mPermsList = new ArrayList<MyPermissionInfo>();
+ this(context);
+ mPackageName = packageName;
Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
PackageInfo pkgInfo;
try {
@@ -264,23 +275,17 @@ public class AppSecurityPermissions {
if((pkgInfo.applicationInfo != null) && (pkgInfo.applicationInfo.uid != -1)) {
getAllUsedPermissions(pkgInfo.applicationInfo.uid, permSet);
}
- for(MyPermissionInfo tmpInfo : permSet) {
- mPermsList.add(tmpInfo);
- }
+ mPermsList.addAll(permSet);
setPermissions(mPermsList);
}
public AppSecurityPermissions(Context context, PackageInfo info) {
- mContext = context;
- mPm = mContext.getPackageManager();
- loadResources();
- mPermComparator = new PermissionInfoComparator();
- mPermGroupComparator = new PermissionGroupInfoComparator();
- mPermsList = new ArrayList<MyPermissionInfo>();
+ this(context);
Set<MyPermissionInfo> permSet = new HashSet<MyPermissionInfo>();
if(info == null) {
return;
}
+ mPackageName = info.packageName;
// Convert to a PackageInfo
PackageInfo installedPkgInfo = null;
@@ -300,23 +305,14 @@ public class AppSecurityPermissions {
sharedUid = mPm.getUidForSharedUser(info.sharedUserId);
getAllUsedPermissions(sharedUid, permSet);
} catch (NameNotFoundException e) {
- Log.w(TAG, "Could'nt retrieve shared user id for:"+info.packageName);
+ Log.w(TAG, "Couldn't retrieve shared user id for: " + info.packageName);
}
}
// Retrieve list of permissions
- for (MyPermissionInfo tmpInfo : permSet) {
- mPermsList.add(tmpInfo);
- }
+ mPermsList.addAll(permSet);
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. This provides
* the old UI layout for permissions; it is only here for the device admin
@@ -332,10 +328,6 @@ public class AppSecurityPermissions {
description, dangerous, icon);
}
- public PackageInfo getInstalledPackageInfo() {
- return mInstalledPackageInfo;
- }
-
private void getAllUsedPermissions(int sharedUid, Set<MyPermissionInfo> permSet) {
String sharedPkgList[] = mPm.getPackagesForUid(sharedUid);
if(sharedPkgList == null || (sharedPkgList.length == 0)) {
@@ -346,17 +338,12 @@ public class AppSecurityPermissions {
}
}
- private void getPermissionsForPackage(String packageName,
- Set<MyPermissionInfo> permSet) {
- PackageInfo pkgInfo;
+ private void getPermissionsForPackage(String packageName, Set<MyPermissionInfo> permSet) {
try {
- pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
- } catch (NameNotFoundException e) {
- Log.w(TAG, "Couldn't retrieve permissions for package:"+packageName);
- return;
- }
- if ((pkgInfo != null) && (pkgInfo.requestedPermissions != null)) {
+ PackageInfo pkgInfo = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
extractPerms(pkgInfo, permSet, pkgInfo);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Couldn't retrieve permissions for package: " + packageName);
}
}
@@ -367,7 +354,6 @@ public class AppSecurityPermissions {
if ((strList == null) || (strList.length == 0)) {
return;
}
- 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
@@ -467,17 +453,23 @@ public class AppSecurityPermissions {
}
public View getPermissionsView() {
- return getPermissionsView(WHICH_ALL);
+ return getPermissionsView(WHICH_ALL, false);
+ }
+
+ public View getPermissionsViewWithRevokeButtons() {
+ return getPermissionsView(WHICH_ALL, true);
}
public View getPermissionsView(int which) {
- mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ return getPermissionsView(which, false);
+ }
+ private View getPermissionsView(int which, boolean showRevokeUI) {
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);
+ displayPermissions(mPermGroupsList, displayList, which, showRevokeUI);
if (displayList.getChildCount() <= 0) {
noPermsView.setVisibility(View.VISIBLE);
}
@@ -490,7 +482,7 @@ public class AppSecurityPermissions {
* list of permission descriptions.
*/
private void displayPermissions(List<MyPermissionGroupInfo> groups,
- LinearLayout permListView, int which) {
+ LinearLayout permListView, int which, boolean showRevokeUI) {
permListView.removeAllViews();
int spacing = (int)(8*mContext.getResources().getDisplayMetrics().density);
@@ -501,7 +493,7 @@ public class AppSecurityPermissions {
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);
+ which != WHICH_NEW ? mNewPermPrefix : null, showRevokeUI);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -520,18 +512,19 @@ public class AppSecurityPermissions {
}
private PermissionItemView getPermissionItemView(MyPermissionGroupInfo grp,
- MyPermissionInfo perm, boolean first, CharSequence newPermPrefix) {
- return getPermissionItemView(mContext, mInflater, grp, perm, first, newPermPrefix);
+ MyPermissionInfo perm, boolean first, CharSequence newPermPrefix, boolean showRevokeUI) {
+ return getPermissionItemView(mContext, mInflater, grp, perm, first, newPermPrefix,
+ mPackageName, showRevokeUI);
}
private static PermissionItemView getPermissionItemView(Context context, LayoutInflater inflater,
MyPermissionGroupInfo grp, MyPermissionInfo perm, boolean first,
- CharSequence newPermPrefix) {
- PermissionItemView permView = (PermissionItemView)inflater.inflate(
+ CharSequence newPermPrefix, String packageName, boolean showRevokeUI) {
+ 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);
+ permView.setPermission(grp, perm, first, newPermPrefix, packageName, showRevokeUI);
return permView;
}
@@ -557,16 +550,27 @@ public class AppSecurityPermissions {
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) {
+ final boolean isNormal = (base == PermissionInfo.PROTECTION_NORMAL);
+ final boolean isDangerous = (base == PermissionInfo.PROTECTION_DANGEROUS);
+ final boolean isRequired =
+ ((newReqFlags&PackageInfo.REQUESTED_PERMISSION_REQUIRED) != 0);
+ final boolean isDevelopment =
+ ((pInfo.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0);
+ final boolean wasGranted =
+ ((existingReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0);
+ final boolean isGranted =
+ ((newReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0);
+
+ // Dangerous and normal permissions are always shown to the user if the permission
+ // is required, or it was previously granted
+ if ((isNormal || isDangerous) && (isRequired || wasGranted || isGranted)) {
return true;
}
+
// 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 (isDevelopment && wasGranted) {
if (localLOGV) Log.i(TAG, "Special perm " + pInfo.name
+ ": protlevel=0x" + Integer.toHexString(pInfo.protectionLevel));
return true;
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index 44f04db..97926a7 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -97,11 +97,11 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* Constructor
*
* @param context The current context.
- * @param textViewResourceId The resource ID for a layout file containing a TextView to use when
+ * @param resource The resource ID for a layout file containing a TextView to use when
* instantiating views.
*/
- public ArrayAdapter(Context context, int textViewResourceId) {
- init(context, textViewResourceId, 0, new ArrayList<T>());
+ public ArrayAdapter(Context context, int resource) {
+ init(context, resource, 0, new ArrayList<T>());
}
/**
@@ -120,12 +120,12 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* Constructor
*
* @param context The current context.
- * @param textViewResourceId The resource ID for a layout file containing a TextView to use when
+ * @param resource The resource ID for a layout file containing a TextView to use when
* instantiating views.
* @param objects The objects to represent in the ListView.
*/
- public ArrayAdapter(Context context, int textViewResourceId, T[] objects) {
- init(context, textViewResourceId, 0, Arrays.asList(objects));
+ public ArrayAdapter(Context context, int resource, T[] objects) {
+ init(context, resource, 0, Arrays.asList(objects));
}
/**
@@ -145,12 +145,12 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* Constructor
*
* @param context The current context.
- * @param textViewResourceId The resource ID for a layout file containing a TextView to use when
+ * @param resource The resource ID for a layout file containing a TextView to use when
* instantiating views.
* @param objects The objects to represent in the ListView.
*/
- public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) {
- init(context, textViewResourceId, 0, objects);
+ public ArrayAdapter(Context context, int resource, List<T> objects) {
+ init(context, resource, 0, objects);
}
/**
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index 361eca4..36d33e7 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -1248,17 +1248,14 @@ public class CalendarView extends FrameLayout {
* @param calendar A day in the new focus month.
*/
private void setMonthDisplayed(Calendar calendar) {
- final int newMonthDisplayed = calendar.get(Calendar.MONTH);
- if (mCurrentMonthDisplayed != newMonthDisplayed) {
- mCurrentMonthDisplayed = newMonthDisplayed;
- mAdapter.setFocusMonth(mCurrentMonthDisplayed);
- final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
- | DateUtils.FORMAT_SHOW_YEAR;
- final long millis = calendar.getTimeInMillis();
- String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
- mMonthName.setText(newMonthName);
- mMonthName.invalidate();
- }
+ mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
+ mAdapter.setFocusMonth(mCurrentMonthDisplayed);
+ final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
+ | DateUtils.FORMAT_SHOW_YEAR;
+ final long millis = calendar.getTimeInMillis();
+ String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
+ mMonthName.setText(newMonthName);
+ mMonthName.invalidate();
}
/**
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 07d3a7a..8f515f5 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -419,7 +419,7 @@ public class DatePicker extends FrameLayout {
* @see #getCalendarView()
*/
public boolean getCalendarViewShown() {
- return mCalendarView.isShown();
+ return (mCalendarView.getVisibility() == View.VISIBLE);
}
/**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 8892316..f57f333 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -20,6 +20,8 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.EditableInputConnection;
import android.R;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
import android.content.ClipData;
import android.content.ClipData.Item;
import android.content.Context;
@@ -124,7 +126,6 @@ public class Editor {
InputMethodState mInputMethodState;
DisplayList[] mTextDisplayLists;
- int mLastLayoutHeight;
boolean mFrozenWithFocus;
boolean mSelectionMoved;
@@ -315,7 +316,7 @@ public class Editor {
private void setErrorIcon(Drawable icon) {
Drawables dr = mTextView.mDrawables;
if (dr == null) {
- mTextView.mDrawables = dr = new Drawables();
+ mTextView.mDrawables = dr = new Drawables(mTextView.getContext());
}
dr.setErrorDrawable(icon, mTextView);
@@ -1289,20 +1290,11 @@ public class Editor {
mTextDisplayLists = new DisplayList[ArrayUtils.idealObjectArraySize(0)];
}
- // If the height of the layout changes (usually when inserting or deleting a line,
- // but could be changes within a span), invalidate everything. We could optimize
- // more aggressively (for example, adding offsets to blocks) but it would be more
- // complex and we would only get the benefit in some cases.
- int layoutHeight = layout.getHeight();
- if (mLastLayoutHeight != layoutHeight) {
- invalidateTextDisplayList();
- mLastLayoutHeight = layoutHeight;
- }
-
DynamicLayout dynamicLayout = (DynamicLayout) layout;
int[] blockEndLines = dynamicLayout.getBlockEndLines();
int[] blockIndices = dynamicLayout.getBlockIndices();
final int numberOfBlocks = dynamicLayout.getNumberOfBlocks();
+ final int indexFirstChangedBlock = dynamicLayout.getIndexFirstChangedBlock();
int endOfPreviousBlock = -1;
int searchStartIndex = 0;
@@ -1324,10 +1316,11 @@ public class Editor {
blockDisplayList = mTextDisplayLists[blockIndex] =
mTextView.getHardwareRenderer().createDisplayList("Text " + blockIndex);
} else {
- if (blockIsInvalid) blockDisplayList.invalidate();
+ if (blockIsInvalid) blockDisplayList.clear();
}
- if (!blockDisplayList.isValid()) {
+ final boolean blockDisplayListIsInvalid = !blockDisplayList.isValid();
+ if (i >= indexFirstChangedBlock || blockDisplayListIsInvalid) {
final int blockBeginLine = endOfPreviousBlock + 1;
final int top = layout.getLineTop(blockBeginLine);
final int bottom = layout.getLineBottom(blockEndLine);
@@ -1344,24 +1337,27 @@ public class Editor {
right = (int) (max + 0.5f);
}
- final HardwareCanvas hardwareCanvas = blockDisplayList.start();
- try {
- // Tighten the bounds of the viewport to the actual text size
- hardwareCanvas.setViewport(right - left, bottom - top);
- // The dirty rect should always be null for a display list
- hardwareCanvas.onPreDraw(null);
- // drawText is always relative to TextView's origin, this translation brings
- // this range of text back to the top left corner of the viewport
- hardwareCanvas.translate(-left, -top);
- layout.drawText(hardwareCanvas, blockBeginLine, blockEndLine);
- // No need to untranslate, previous context is popped after drawDisplayList
- } finally {
- hardwareCanvas.onPostDraw();
- blockDisplayList.end();
- blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
- // Same as drawDisplayList below, handled by our TextView's parent
- blockDisplayList.setClipChildren(false);
+ // Rebuild display list if it is invalid
+ if (blockDisplayListIsInvalid) {
+ final HardwareCanvas hardwareCanvas = blockDisplayList.start(
+ right - left, bottom - top);
+ try {
+ // drawText is always relative to TextView's origin, this translation
+ // brings this range of text back to the top left corner of the viewport
+ hardwareCanvas.translate(-left, -top);
+ layout.drawText(hardwareCanvas, blockBeginLine, blockEndLine);
+ // No need to untranslate, previous context is popped after
+ // drawDisplayList
+ } finally {
+ blockDisplayList.end();
+ // Same as drawDisplayList below, handled by our TextView's parent
+ blockDisplayList.setClipToBounds(false);
+ }
}
+
+ // Valid disply list whose index is >= indexFirstChangedBlock
+ // only needs to update its drawing location.
+ blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
}
((HardwareCanvas) canvas).drawDisplayList(blockDisplayList, null,
@@ -1369,6 +1365,8 @@ public class Editor {
endOfPreviousBlock = blockEndLine;
}
+
+ dynamicLayout.setIndexFirstChangedBlock(numberOfBlocks);
} else {
// Boring layout is used for empty and hint text
layout.drawText(canvas, firstLine, lastLine);
@@ -1431,7 +1429,7 @@ public class Editor {
while (i < numberOfBlocks) {
final int blockIndex = blockIndices[i];
if (blockIndex != DynamicLayout.INVALID_BLOCK_INDEX) {
- mTextDisplayLists[blockIndex].invalidate();
+ mTextDisplayLists[blockIndex].clear();
}
if (blockEndLines[i] >= lastLine) break;
i++;
@@ -1442,7 +1440,7 @@ public class Editor {
void invalidateTextDisplayList() {
if (mTextDisplayLists != null) {
for (int i = 0; i < mTextDisplayLists.length; i++) {
- if (mTextDisplayLists[i] != null) mTextDisplayLists[i].invalidate();
+ if (mTextDisplayLists[i] != null) mTextDisplayLists[i].clear();
}
}
}
@@ -1468,20 +1466,24 @@ public class Editor {
middle = (top + bottom) >> 1;
}
- updateCursorPosition(0, top, middle, getPrimaryHorizontal(layout, hintLayout, offset));
+ boolean clamped = layout.shouldClampCursor(line);
+ updateCursorPosition(0, top, middle,
+ getPrimaryHorizontal(layout, hintLayout, offset, clamped));
if (mCursorCount == 2) {
- updateCursorPosition(1, middle, bottom, layout.getSecondaryHorizontal(offset));
+ updateCursorPosition(1, middle, bottom,
+ layout.getSecondaryHorizontal(offset, clamped));
}
}
- private float getPrimaryHorizontal(Layout layout, Layout hintLayout, int offset) {
+ private float getPrimaryHorizontal(Layout layout, Layout hintLayout, int offset,
+ boolean clamped) {
if (TextUtils.isEmpty(layout.getText()) &&
hintLayout != null &&
!TextUtils.isEmpty(hintLayout.getText())) {
- return hintLayout.getPrimaryHorizontal(offset);
+ return hintLayout.getPrimaryHorizontal(offset, clamped);
} else {
- return layout.getPrimaryHorizontal(offset);
+ return layout.getPrimaryHorizontal(offset, clamped);
}
}
@@ -1890,10 +1892,23 @@ public class Editor {
// Make sure there is only at most one EasyEditSpan in the text
if (mPopupWindow.mEasyEditSpan != null) {
- text.removeSpan(mPopupWindow.mEasyEditSpan);
+ mPopupWindow.mEasyEditSpan.setDeleteEnabled(false);
}
mPopupWindow.setEasyEditSpan((EasyEditSpan) span);
+ mPopupWindow.setOnDeleteListener(new EasyEditDeleteListener() {
+ @Override
+ public void onDeleteClick(EasyEditSpan span) {
+ Editable editable = (Editable) mTextView.getText();
+ int start = editable.getSpanStart(span);
+ int end = editable.getSpanEnd(span);
+ if (start >= 0 && end >= 0) {
+ sendNotification(EasyEditSpan.TEXT_DELETED, span);
+ mTextView.deleteText_internal(start, end);
+ }
+ editable.removeSpan(span);
+ }
+ });
if (mTextView.getWindowVisibility() != View.VISIBLE) {
// The window is not visible yet, ignore the text change.
@@ -1927,8 +1942,10 @@ public class Editor {
@Override
public void onSpanChanged(Spannable text, Object span, int previousStart, int previousEnd,
int newStart, int newEnd) {
- if (mPopupWindow != null && span == mPopupWindow.mEasyEditSpan) {
- text.removeSpan(mPopupWindow.mEasyEditSpan);
+ if (mPopupWindow != null && span instanceof EasyEditSpan) {
+ EasyEditSpan easyEditSpan = (EasyEditSpan) span;
+ sendNotification(EasyEditSpan.TEXT_MODIFIED, easyEditSpan);
+ text.removeSpan(easyEditSpan);
}
}
@@ -1938,6 +1955,31 @@ public class Editor {
mTextView.removeCallbacks(mHidePopup);
}
}
+
+ private void sendNotification(int textChangedType, EasyEditSpan span) {
+ try {
+ PendingIntent pendingIntent = span.getPendingIntent();
+ if (pendingIntent != null) {
+ Intent intent = new Intent();
+ intent.putExtra(EasyEditSpan.EXTRA_TEXT_CHANGED_TYPE, textChangedType);
+ pendingIntent.send(mTextView.getContext(), 0, intent);
+ }
+ } catch (CanceledException e) {
+ // This should not happen, as we should try to send the intent only once.
+ Log.w(TAG, "PendingIntent for notification cannot be sent", e);
+ }
+ }
+ }
+
+ /**
+ * Listens for the delete event triggered by {@link EasyEditPopupWindow}.
+ */
+ private interface EasyEditDeleteListener {
+
+ /**
+ * Clicks the delete pop-up.
+ */
+ void onDeleteClick(EasyEditSpan span);
}
/**
@@ -1950,6 +1992,7 @@ public class Editor {
com.android.internal.R.layout.text_edit_action_popup_text;
private TextView mDeleteTextView;
private EasyEditSpan mEasyEditSpan;
+ private EasyEditDeleteListener mOnDeleteListener;
@Override
protected void createPopupWindow() {
@@ -1984,16 +2027,26 @@ public class Editor {
mEasyEditSpan = easyEditSpan;
}
+ private void setOnDeleteListener(EasyEditDeleteListener listener) {
+ mOnDeleteListener = listener;
+ }
+
@Override
public void onClick(View view) {
- if (view == mDeleteTextView) {
- Editable editable = (Editable) mTextView.getText();
- int start = editable.getSpanStart(mEasyEditSpan);
- int end = editable.getSpanEnd(mEasyEditSpan);
- if (start >= 0 && end >= 0) {
- mTextView.deleteText_internal(start, end);
- }
+ if (view == mDeleteTextView
+ && mEasyEditSpan != null && mEasyEditSpan.isDeleteEnabled()
+ && mOnDeleteListener != null) {
+ mOnDeleteListener.onDeleteClick(mEasyEditSpan);
+ }
+ }
+
+ @Override
+ public void hide() {
+ if (mEasyEditSpan != null) {
+ mEasyEditSpan.setDeleteEnabled(false);
}
+ mOnDeleteListener = null;
+ super.hide();
}
@Override
@@ -2647,15 +2700,10 @@ public class Editor {
suggestionStart, suggestionEnd).toString();
mTextView.replaceText_internal(spanStart, spanEnd, suggestion);
- // Notify source IME of the suggestion pick. Do this before swaping texts.
- if (!TextUtils.isEmpty(
- suggestionInfo.suggestionSpan.getNotificationTargetClassName())) {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText,
- suggestionInfo.suggestionIndex);
- }
- }
+ // Notify source IME of the suggestion pick. Do this before
+ // swaping texts.
+ suggestionInfo.suggestionSpan.notifySelection(
+ mTextView.getContext(), originalText, suggestionInfo.suggestionIndex);
// Swap text content between actual text and Suggestion span
String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions();
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index a746370..7b81aa8 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -36,6 +36,8 @@ import android.widget.ExpandableListConnector.PositionMetadata;
import java.util.ArrayList;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
/**
* A view that shows items in a vertically scrolling two-level list. This
* differs from the {@link ListView} by allowing two levels: groups which can
@@ -76,6 +78,10 @@ import java.util.ArrayList;
* @attr ref android.R.styleable#ExpandableListView_childIndicatorLeft
* @attr ref android.R.styleable#ExpandableListView_childIndicatorRight
* @attr ref android.R.styleable#ExpandableListView_childDivider
+ * @attr ref android.R.styleable#ExpandableListView_indicatorStart
+ * @attr ref android.R.styleable#ExpandableListView_indicatorEnd
+ * @attr ref android.R.styleable#ExpandableListView_childIndicatorStart
+ * @attr ref android.R.styleable#ExpandableListView_childIndicatorEnd
*/
public class ExpandableListView extends ListView {
@@ -134,6 +140,12 @@ public class ExpandableListView extends ListView {
/** Right bound for drawing the indicator. */
private int mIndicatorRight;
+ /** Start bound for drawing the indicator. */
+ private int mIndicatorStart;
+
+ /** End bound for drawing the indicator. */
+ private int mIndicatorEnd;
+
/**
* Left bound for drawing the indicator of a child. Value of
* {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorLeft.
@@ -147,11 +159,28 @@ public class ExpandableListView extends ListView {
private int mChildIndicatorRight;
/**
+ * Start bound for drawing the indicator of a child. Value of
+ * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorStart.
+ */
+ private int mChildIndicatorStart;
+
+ /**
+ * End bound for drawing the indicator of a child. Value of
+ * {@link #CHILD_INDICATOR_INHERIT} means use mIndicatorEnd.
+ */
+ private int mChildIndicatorEnd;
+
+ /**
* Denotes when a child indicator should inherit this bound from the generic
* indicator bounds
*/
public static final int CHILD_INDICATOR_INHERIT = -1;
-
+
+ /**
+ * Denotes an undefined value for an indicator
+ */
+ private static final int INDICATOR_UNDEFINED = -2;
+
/** The indicator drawn next to a group. */
private Drawable mGroupIndicator;
@@ -202,30 +231,118 @@ public class ExpandableListView extends ListView {
super(context, attrs, defStyle);
TypedArray a =
- context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ExpandableListView, defStyle,
- 0);
-
- mGroupIndicator = a
- .getDrawable(com.android.internal.R.styleable.ExpandableListView_groupIndicator);
- mChildIndicator = a
- .getDrawable(com.android.internal.R.styleable.ExpandableListView_childIndicator);
- mIndicatorLeft = a
- .getDimensionPixelSize(com.android.internal.R.styleable.ExpandableListView_indicatorLeft, 0);
- mIndicatorRight = a
- .getDimensionPixelSize(com.android.internal.R.styleable.ExpandableListView_indicatorRight, 0);
+ context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ExpandableListView, defStyle, 0);
+
+ mGroupIndicator = a.getDrawable(
+ com.android.internal.R.styleable.ExpandableListView_groupIndicator);
+ mChildIndicator = a.getDrawable(
+ com.android.internal.R.styleable.ExpandableListView_childIndicator);
+ mIndicatorLeft = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_indicatorLeft, 0);
+ mIndicatorRight = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_indicatorRight, 0);
if (mIndicatorRight == 0 && mGroupIndicator != null) {
mIndicatorRight = mIndicatorLeft + mGroupIndicator.getIntrinsicWidth();
}
mChildIndicatorLeft = a.getDimensionPixelSize(
- com.android.internal.R.styleable.ExpandableListView_childIndicatorLeft, CHILD_INDICATOR_INHERIT);
+ com.android.internal.R.styleable.ExpandableListView_childIndicatorLeft,
+ CHILD_INDICATOR_INHERIT);
mChildIndicatorRight = a.getDimensionPixelSize(
- com.android.internal.R.styleable.ExpandableListView_childIndicatorRight, CHILD_INDICATOR_INHERIT);
- mChildDivider = a.getDrawable(com.android.internal.R.styleable.ExpandableListView_childDivider);
-
+ com.android.internal.R.styleable.ExpandableListView_childIndicatorRight,
+ CHILD_INDICATOR_INHERIT);
+ mChildDivider = a.getDrawable(
+ com.android.internal.R.styleable.ExpandableListView_childDivider);
+
+ if (!isRtlCompatibilityMode()) {
+ mIndicatorStart = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_indicatorStart,
+ INDICATOR_UNDEFINED);
+ mIndicatorEnd = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_indicatorEnd,
+ INDICATOR_UNDEFINED);
+
+ mChildIndicatorStart = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_childIndicatorStart,
+ CHILD_INDICATOR_INHERIT);
+ mChildIndicatorEnd = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ExpandableListView_childIndicatorEnd,
+ CHILD_INDICATOR_INHERIT);
+ }
+
a.recycle();
}
-
-
+
+ /**
+ * Return true if we are in RTL compatibility mode (either before Jelly Bean MR1 or
+ * RTL not supported)
+ */
+ private boolean isRtlCompatibilityMode() {
+ final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+ return targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport();
+ }
+
+ /**
+ * Return true if the application tag in the AndroidManifest has set "supportRtl" to true
+ */
+ private boolean hasRtlSupport() {
+ return mContext.getApplicationInfo().hasRtlSupport();
+ }
+
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ resolveIndicator();
+ resolveChildIndicator();
+ }
+
+ /**
+ * Resolve start/end indicator. start/end indicator always takes precedence over left/right
+ * indicator when defined.
+ */
+ private void resolveIndicator() {
+ final boolean isLayoutRtl = isLayoutRtl();
+ if (isLayoutRtl) {
+ if (mIndicatorStart >= 0) {
+ mIndicatorRight = mIndicatorStart;
+ }
+ if (mIndicatorEnd >= 0) {
+ mIndicatorLeft = mIndicatorEnd;
+ }
+ } else {
+ if (mIndicatorStart >= 0) {
+ mIndicatorLeft = mIndicatorStart;
+ }
+ if (mIndicatorEnd >= 0) {
+ mIndicatorRight = mIndicatorEnd;
+ }
+ }
+ if (mIndicatorRight == 0 && mGroupIndicator != null) {
+ mIndicatorRight = mIndicatorLeft + mGroupIndicator.getIntrinsicWidth();
+ }
+ }
+
+ /**
+ * Resolve start/end child indicator. start/end child indicator always takes precedence over
+ * left/right child indicator when defined.
+ */
+ private void resolveChildIndicator() {
+ final boolean isLayoutRtl = isLayoutRtl();
+ if (isLayoutRtl) {
+ if (mChildIndicatorStart >= CHILD_INDICATOR_INHERIT) {
+ mChildIndicatorRight = mChildIndicatorStart;
+ }
+ if (mChildIndicatorEnd >= CHILD_INDICATOR_INHERIT) {
+ mChildIndicatorLeft = mChildIndicatorEnd;
+ }
+ } else {
+ if (mChildIndicatorStart >= CHILD_INDICATOR_INHERIT) {
+ mChildIndicatorLeft = mChildIndicatorStart;
+ }
+ if (mChildIndicatorEnd >= CHILD_INDICATOR_INHERIT) {
+ mChildIndicatorRight = mChildIndicatorEnd;
+ }
+ }
+ }
+
@Override
protected void dispatchDraw(Canvas canvas) {
// Draw children, etc.
@@ -288,6 +405,9 @@ public class ExpandableListView extends ListView {
// Get more expandable list-related info for this item
pos = mConnector.getUnflattenedPos(childFlPos);
+ final boolean isLayoutRtl = isLayoutRtl();
+ final int width = getWidth();
+
// If this item type and the previous item type are different, then we need to change
// the left & right bounds
if (pos.position.type != lastItemType) {
@@ -300,9 +420,18 @@ public class ExpandableListView extends ListView {
indicatorRect.left = mIndicatorLeft;
indicatorRect.right = mIndicatorRight;
}
-
- indicatorRect.left += mPaddingLeft;
- indicatorRect.right += mPaddingLeft;
+
+ if (isLayoutRtl) {
+ final int temp = indicatorRect.left;
+ indicatorRect.left = width - indicatorRect.right;
+ indicatorRect.right = width - temp;
+
+ indicatorRect.left -= mPaddingRight;
+ indicatorRect.right -= mPaddingRight;
+ } else {
+ indicatorRect.left += mPaddingLeft;
+ indicatorRect.right += mPaddingLeft;
+ }
lastItemType = pos.position.type;
}
@@ -1041,8 +1170,26 @@ public class ExpandableListView extends ListView {
public void setChildIndicatorBounds(int left, int right) {
mChildIndicatorLeft = left;
mChildIndicatorRight = right;
+ resolveChildIndicator();
}
-
+
+ /**
+ * Sets the relative drawing bounds for the child indicator. For either, you can
+ * specify {@link #CHILD_INDICATOR_INHERIT} to use inherit from the general
+ * indicator's bounds.
+ *
+ * @see #setIndicatorBounds(int, int)
+ * @param start The start position (relative to the start bounds of this View)
+ * to start drawing the indicator.
+ * @param end The end position (relative to the end bounds of this
+ * View) to end the drawing of the indicator.
+ */
+ public void setChildIndicatorBoundsRelative(int start, int end) {
+ mChildIndicatorStart = start;
+ mChildIndicatorEnd = end;
+ resolveChildIndicator();
+ }
+
/**
* Sets the indicator to be drawn next to a group.
*
@@ -1072,8 +1219,26 @@ public class ExpandableListView extends ListView {
public void setIndicatorBounds(int left, int right) {
mIndicatorLeft = left;
mIndicatorRight = right;
+ resolveIndicator();
}
-
+
+ /**
+ * Sets the relative drawing bounds for the indicators (at minimum, the group indicator
+ * is affected by this; the child indicator is affected by this if the
+ * child indicator bounds are set to inherit).
+ *
+ * @see #setChildIndicatorBounds(int, int)
+ * @param start The start position (relative to the start bounds of this View)
+ * to start drawing the indicator.
+ * @param end The end position (relative to the end bounds of this
+ * View) to end the drawing of the indicator.
+ */
+ public void setIndicatorBoundsRelative(int start, int end) {
+ mIndicatorStart = start;
+ mIndicatorEnd = end;
+ resolveIndicator();
+ }
+
/**
* Extra menu information specific to an {@link ExpandableListView} provided
* to the
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index d2139af..fc9c000 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -216,8 +216,22 @@ class FastScroller {
mHandler.removeCallbacks(mScrollFade);
break;
case STATE_EXIT:
- int viewWidth = mList.getWidth();
- mList.invalidate(viewWidth - mThumbW, mThumbY, viewWidth, mThumbY + mThumbH);
+ final int viewWidth = mList.getWidth();
+ final int top = mThumbY;
+ final int bottom = mThumbY + mThumbH;
+ final int left;
+ final int right;
+ switch (mList.getLayoutDirection()) {
+ case View.LAYOUT_DIRECTION_RTL:
+ left = 0;
+ right = mThumbW;
+ break;
+ case View.LAYOUT_DIRECTION_LTR:
+ default:
+ left = viewWidth - mThumbW;
+ right = viewWidth;
+ }
+ mList.invalidate(left, top, right, bottom);
break;
}
mState = state;
@@ -398,10 +412,26 @@ class FastScroller {
} else if (mState == STATE_EXIT) {
if (alpha == 0) { // Done with exit
setState(STATE_NONE);
- } else if (mTrackDrawable != null) {
- mList.invalidate(viewWidth - mThumbW, 0, viewWidth, mList.getHeight());
} else {
- mList.invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
+ final int left, right, top, bottom;
+ if (mTrackDrawable != null) {
+ top = 0;
+ bottom = mList.getHeight();
+ } else {
+ top = y;
+ bottom = y + mThumbH;
+ }
+ switch (mList.getLayoutDirection()) {
+ case View.LAYOUT_DIRECTION_RTL:
+ left = 0;
+ right = mThumbW;
+ break;
+ case View.LAYOUT_DIRECTION_LTR:
+ default:
+ left = viewWidth - mThumbW;
+ right = viewWidth;
+ }
+ mList.invalidate(left, top, right, bottom);
}
}
}
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index e0c5bbd..c4ef11c 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -891,7 +891,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
lp = (Gallery.LayoutParams) generateDefaultLayoutParams();
}
- addViewInLayout(child, fromLeft != mIsRtl ? -1 : 0, lp);
+ addViewInLayout(child, fromLeft != mIsRtl ? -1 : 0, lp, true);
child.setSelected(offset == 0);
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 772d748..2309001 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -605,7 +605,7 @@ public class GridLayout extends ViewGroup {
}
private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) {
- return isAtEdge ? DEFAULT_CONTAINER_MARGIN : getDefaultMargin(c, horizontal, leading);
+ return /*isAtEdge ? DEFAULT_CONTAINER_MARGIN :*/ getDefaultMargin(c, horizontal, leading);
}
private int getDefaultMargin(View c, LayoutParams p, boolean horizontal, boolean leading) {
@@ -824,13 +824,11 @@ public class GridLayout extends ViewGroup {
// Draw grid
private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) {
- int dx = getPaddingLeft();
- int dy = getPaddingTop();
if (isLayoutRtl()) {
int width = getWidth();
- graphics.drawLine(width - dx - x1, dy + y1, width - dx - x2, dy + y2, paint);
+ graphics.drawLine(width - x1, y1, width - x2, y2, paint);
} else {
- graphics.drawLine(dx + x1, dy + y1, dx + x2, dy + y2, paint);
+ graphics.drawLine(x1, y1, x2, y2, paint);
}
}
@@ -838,18 +836,17 @@ public class GridLayout extends ViewGroup {
* @hide
*/
@Override
- protected void onDebugDrawMargins(Canvas canvas) {
+ protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
// Apply defaults, so as to remove UNDEFINED values
LayoutParams lp = new LayoutParams();
for (int i = 0; i < getChildCount(); i++) {
View c = getChildAt(i);
- Insets insets = getLayoutMode() == OPTICAL_BOUNDS ? c.getOpticalInsets() : Insets.NONE;
lp.setMargins(
- getMargin1(c, true, true) - insets.left,
- getMargin1(c, false, true) - insets.top,
- getMargin1(c, true, false) - insets.right,
- getMargin1(c, false, false) - insets.bottom);
- lp.onDebugDraw(c, canvas);
+ getMargin1(c, true, true),
+ getMargin1(c, false, true),
+ getMargin1(c, true, false),
+ getMargin1(c, false, false));
+ lp.onDebugDraw(c, canvas, paint);
}
}
@@ -858,26 +855,30 @@ public class GridLayout extends ViewGroup {
*/
@Override
protected void onDebugDraw(Canvas canvas) {
- int height = getHeight() - getPaddingTop() - getPaddingBottom();
- int width = getWidth() - getPaddingLeft() - getPaddingRight();
-
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.argb(50, 255, 255, 255));
+ Insets insets = getOpticalInsets();
+
+ int top = getPaddingTop() + insets.top;
+ int left = getPaddingLeft() + insets.left;
+ int right = getWidth() - getPaddingRight() - insets.right;
+ int bottom = getHeight() - getPaddingBottom() - insets.bottom;
+
int[] xs = horizontalAxis.locations;
if (xs != null) {
for (int i = 0, length = xs.length; i < length; i++) {
- int x = xs[i];
- drawLine(canvas, x, 0, x, height - 1, paint);
+ int x = left + xs[i];
+ drawLine(canvas, x, top, x, bottom, paint);
}
}
int[] ys = verticalAxis.locations;
if (ys != null) {
for (int i = 0, length = ys.length; i < length; i++) {
- int y = ys[i];
- drawLine(canvas, 0, y, width - 1, y, paint);
+ int y = top + ys[i];
+ drawLine(canvas, left, y, right, y, paint);
}
}
@@ -943,15 +944,17 @@ public class GridLayout extends ViewGroup {
// Measurement
+ // Note: padding has already been removed from the supplied specs
private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec,
int childWidth, int childHeight) {
int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
- mPaddingLeft + mPaddingRight + getTotalMargin(child, true), childWidth);
+ getTotalMargin(child, true), childWidth);
int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
- mPaddingTop + mPaddingBottom + getTotalMargin(child, false), childHeight);
+ getTotalMargin(child, false), childHeight);
child.measure(childWidthSpec, childHeightSpec);
}
+ // Note: padding has already been removed from the supplied specs
private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) {
for (int i = 0, N = getChildCount(); i < N; i++) {
View c = getChildAt(i);
@@ -978,6 +981,11 @@ public class GridLayout extends ViewGroup {
}
}
+ static int adjust(int measureSpec, int delta) {
+ return makeMeasureSpec(
+ MeasureSpec.getSize(measureSpec + delta), MeasureSpec.getMode(measureSpec));
+ }
+
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
consistencyCheck();
@@ -986,39 +994,38 @@ public class GridLayout extends ViewGroup {
* is likely to have changed. We must invalidate if so. */
invalidateValues();
- measureChildrenWithMargins(widthSpec, heightSpec, true);
+ int hPadding = getPaddingLeft() + getPaddingRight();
+ int vPadding = getPaddingTop() + getPaddingBottom();
+
+ int widthSpecSansPadding = adjust( widthSpec, -hPadding);
+ int heightSpecSansPadding = adjust(heightSpec, -vPadding);
+
+ measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, true);
- int width, height;
+ int widthSansPadding;
+ int heightSansPadding;
// Use the orientation property to decide which axis should be laid out first.
if (orientation == HORIZONTAL) {
- width = horizontalAxis.getMeasure(widthSpec);
- measureChildrenWithMargins(widthSpec, heightSpec, false);
- height = verticalAxis.getMeasure(heightSpec);
+ widthSansPadding = horizontalAxis.getMeasure(widthSpecSansPadding);
+ measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
+ heightSansPadding = verticalAxis.getMeasure(heightSpecSansPadding);
} else {
- height = verticalAxis.getMeasure(heightSpec);
- measureChildrenWithMargins(widthSpec, heightSpec, false);
- width = horizontalAxis.getMeasure(widthSpec);
+ heightSansPadding = verticalAxis.getMeasure(heightSpecSansPadding);
+ measureChildrenWithMargins(widthSpecSansPadding, heightSpecSansPadding, false);
+ widthSansPadding = horizontalAxis.getMeasure(widthSpecSansPadding);
}
- int hPadding = getPaddingLeft() + getPaddingRight();
- int vPadding = getPaddingTop() + getPaddingBottom();
-
- int measuredWidth = Math.max(hPadding + width, getSuggestedMinimumWidth());
- int measuredHeight = Math.max(vPadding + height, getSuggestedMinimumHeight());
+ int measuredWidth = Math.max(widthSansPadding + hPadding, getSuggestedMinimumWidth());
+ int measuredHeight = Math.max(heightSansPadding + vPadding, getSuggestedMinimumHeight());
setMeasuredDimension(
- resolveSizeAndState(measuredWidth, widthSpec, 0),
+ resolveSizeAndState(measuredWidth, widthSpec, 0),
resolveSizeAndState(measuredHeight, heightSpec, 0));
}
private int getMeasurement(View c, boolean horizontal) {
- int result = horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
- if (getLayoutMode() == OPTICAL_BOUNDS) {
- Insets insets = c.getOpticalInsets();
- return result - (horizontal ? insets.left + insets.right : insets.top + insets.bottom);
- }
- return result;
+ return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
}
final int getMeasurementIncludingMargin(View c, boolean horizontal) {
@@ -1124,14 +1131,6 @@ public class GridLayout extends ViewGroup {
targetWidth - width - paddingRight - rightMargin - dx;
int cy = paddingTop + y1 + gravityOffsetY + alignmentOffsetY + topMargin;
- boolean useLayoutBounds = getLayoutMode() == OPTICAL_BOUNDS;
- if (useLayoutBounds) {
- Insets insets = c.getOpticalInsets();
- cx -= insets.left;
- cy -= insets.top;
- width += (insets.left + insets.right);
- height += (insets.top + insets.bottom);
- }
if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) {
c.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
}
@@ -2418,6 +2417,8 @@ public class GridLayout extends ViewGroup {
* <li> {@code spec.span = [start, start + size]} </li>
* <li> {@code spec.alignment = alignment} </li>
* </ul>
+ * <p>
+ * To leave the start index undefined, use the value {@link #UNDEFINED}.
*
* @param start the start
* @param size the size
@@ -2433,9 +2434,13 @@ public class GridLayout extends ViewGroup {
* <li> {@code spec.span = [start, start + 1]} </li>
* <li> {@code spec.alignment = alignment} </li>
* </ul>
+ * <p>
+ * To leave the start index undefined, use the value {@link #UNDEFINED}.
*
* @param start the start index
* @param alignment the alignment
+ *
+ * @see #spec(int, int, Alignment)
*/
public static Spec spec(int start, Alignment alignment) {
return spec(start, 1, alignment);
@@ -2446,9 +2451,13 @@ public class GridLayout extends ViewGroup {
* <ul>
* <li> {@code spec.span = [start, start + size]} </li>
* </ul>
+ * <p>
+ * To leave the start index undefined, use the value {@link #UNDEFINED}.
*
* @param start the start
* @param size the size
+ *
+ * @see #spec(int, Alignment)
*/
public static Spec spec(int start, int size) {
return spec(start, size, UNDEFINED_ALIGNMENT);
@@ -2459,8 +2468,12 @@ public class GridLayout extends ViewGroup {
* <ul>
* <li> {@code spec.span = [start, start + 1]} </li>
* </ul>
+ * <p>
+ * To leave the start index undefined, use the value {@link #UNDEFINED}.
*
* @param start the start index
+ *
+ * @see #spec(int, int)
*/
public static Spec spec(int start) {
return spec(start, 1);
@@ -2654,14 +2667,7 @@ public class GridLayout extends ViewGroup {
@Override
public int getAlignmentValue(View view, int viewSize, int mode) {
int baseline = view.getBaseline();
- if (baseline == -1) {
- return UNDEFINED;
- } else {
- if (mode == OPTICAL_BOUNDS) {
- return baseline - view.getOpticalInsets().top;
- }
- return baseline;
- }
+ return baseline == -1 ? UNDEFINED : baseline;
}
@Override
diff --git a/core/java/android/widget/HeaderViewListAdapter.java b/core/java/android/widget/HeaderViewListAdapter.java
index 0685e61..e2a269e 100644
--- a/core/java/android/widget/HeaderViewListAdapter.java
+++ b/core/java/android/widget/HeaderViewListAdapter.java
@@ -79,8 +79,7 @@ public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
}
public boolean isEmpty() {
- return (mAdapter == null || mAdapter.isEmpty())
- && getFootersCount() + getHeadersCount() == 0;
+ return mAdapter == null || mAdapter.isEmpty();
}
private boolean areAllListInfosSelectable(ArrayList<ListView.FixedViewInfo> infos) {
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 26c801f..33fd8ce 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -30,6 +30,7 @@ import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -40,6 +41,9 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
+import java.io.IOException;
+import java.io.InputStream;
+
/**
* Displays an arbitrary image, such as an icon. The ImageView class
* can load images from various sources (such as resources or content
@@ -90,6 +94,9 @@ public class ImageView extends View {
private int mBaseline = -1;
private boolean mBaselineAlignBottom = false;
+ // AdjustViewBounds behavior will be in compatibility mode for older apps.
+ private boolean mAdjustViewBoundsCompat = false;
+
private static final ScaleType[] sScaleTypeArray = {
ScaleType.MATRIX,
ScaleType.FIT_XY,
@@ -164,6 +171,8 @@ public class ImageView extends View {
private void initImageView() {
mMatrix = new Matrix();
mScaleType = ScaleType.FIT_CENTER;
+ mAdjustViewBoundsCompat = mContext.getApplicationInfo().targetSdkVersion <=
+ Build.VERSION_CODES.JELLY_BEAN_MR1;
}
@Override
@@ -195,7 +204,7 @@ public class ImageView extends View {
@Override
public boolean hasOverlappingRendering() {
- return (getBackground() != null);
+ return (getBackground() != null && getBackground().getCurrent() != null);
}
@Override
@@ -225,8 +234,15 @@ public class ImageView extends View {
/**
* Set this to true if you want the ImageView to adjust its bounds
* to preserve the aspect ratio of its drawable.
+ *
+ * <p><strong>Note:</strong> If the application targets API level 17 or lower,
+ * adjustViewBounds will allow the drawable to shrink the view bounds, but not grow
+ * to fill available measured space in all cases. This is for compatibility with
+ * legacy {@link android.view.View.MeasureSpec MeasureSpec} and
+ * {@link android.widget.RelativeLayout RelativeLayout} behavior.</p>
+ *
* @param adjustViewBounds Whether to adjust the bounds of this view
- * to presrve the original aspect ratio of the drawable
+ * to preserve the original aspect ratio of the drawable.
*
* @see #getAdjustViewBounds()
*
@@ -332,7 +348,7 @@ public class ImageView extends View {
* {@link #setImageBitmap(android.graphics.Bitmap)} and
* {@link android.graphics.BitmapFactory} instead.</p>
*
- * @param resId the resource identifier of the the drawable
+ * @param resId the resource identifier of the drawable
*
* @attr ref android.R.styleable#ImageView_src
*/
@@ -546,13 +562,14 @@ public class ImageView extends View {
/** Return the view's optional matrix. This is applied to the
view's drawable when it is drawn. If there is not matrix,
- this method will return null.
- Do not change this matrix in place. If you want a different matrix
- applied to the drawable, be sure to call setImageMatrix().
+ this method will return an identity matrix.
+ Do not change this matrix in place but make a copy.
+ If you want a different matrix applied to the drawable,
+ be sure to call setImageMatrix().
*/
public Matrix getImageMatrix() {
if (mDrawMatrix == null) {
- return Matrix.IDENTITY_MATRIX;
+ return new Matrix(Matrix.IDENTITY_MATRIX);
}
return mDrawMatrix;
}
@@ -635,20 +652,27 @@ public class ImageView extends View {
}
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
|| ContentResolver.SCHEME_FILE.equals(scheme)) {
+ InputStream stream = null;
try {
- d = Drawable.createFromStream(
- mContext.getContentResolver().openInputStream(mUri),
- null);
+ stream = mContext.getContentResolver().openInputStream(mUri);
+ d = Drawable.createFromStream(stream, null);
} catch (Exception e) {
Log.w("ImageView", "Unable to open content: " + mUri, e);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ Log.w("ImageView", "Unable to close content: " + mUri, e);
+ }
+ }
}
- } else {
+ } else {
d = Drawable.createFromPath(mUri.toString());
}
if (d == null) {
- System.out.println("resolveUri failed on bad bitmap uri: "
- + mUri);
+ System.out.println("resolveUri failed on bad bitmap uri: " + mUri);
// Don't try again.
mUri = null;
}
@@ -792,6 +816,12 @@ public class ImageView extends View {
if (resizeWidth) {
int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
pleft + pright;
+
+ // Allow the width to outgrow its original estimate if height is fixed.
+ if (!resizeHeight && !mAdjustViewBoundsCompat) {
+ widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
+ }
+
if (newWidth <= widthSize) {
widthSize = newWidth;
done = true;
@@ -802,6 +832,13 @@ public class ImageView extends View {
if (!done && resizeHeight) {
int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
ptop + pbottom;
+
+ // Allow the height to outgrow its original estimate if width is fixed.
+ if (!resizeWidth && !mAdjustViewBoundsCompat) {
+ heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
+ heightMeasureSpec);
+ }
+
if (newHeight <= heightSize) {
heightSize = newHeight;
}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 36dd13c..bc57c36 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -1431,9 +1431,9 @@ public class LinearLayout extends ViewGroup {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
- layoutVertical();
+ layoutVertical(l, t, r, b);
} else {
- layoutHorizontal();
+ layoutHorizontal(l, t, r, b);
}
}
@@ -1444,15 +1444,19 @@ public class LinearLayout extends ViewGroup {
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onLayout(boolean, int, int, int, int)
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
*/
- void layoutVertical() {
+ void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
- final int width = mRight - mLeft;
+ final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
@@ -1466,12 +1470,12 @@ public class LinearLayout extends ViewGroup {
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
- childTop = mPaddingTop + mBottom - mTop - mTotalLength;
+ childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
- childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2;
+ childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
@@ -1534,8 +1538,12 @@ public class LinearLayout extends ViewGroup {
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onLayout(boolean, int, int, int, int)
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
*/
- void layoutHorizontal() {
+ void layoutHorizontal(int left, int top, int right, int bottom) {
final boolean isLayoutRtl = isLayoutRtl();
final int paddingTop = mPaddingTop;
@@ -1543,7 +1551,7 @@ public class LinearLayout extends ViewGroup {
int childLeft;
// Where bottom of child should go
- final int height = mBottom - mTop;
+ final int height = bottom - top;
int childBottom = height - mPaddingBottom;
// Space available for child
@@ -1563,12 +1571,12 @@ public class LinearLayout extends ViewGroup {
switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) {
case Gravity.RIGHT:
// mTotalLength contains the padding already
- childLeft = mPaddingLeft + mRight - mLeft - mTotalLength;
+ childLeft = mPaddingLeft + right - left - mTotalLength;
break;
case Gravity.CENTER_HORIZONTAL:
// mTotalLength contains the padding already
- childLeft = mPaddingLeft + (mRight - mLeft - mTotalLength) / 2;
+ childLeft = mPaddingLeft + (right - left - mTotalLength) / 2;
break;
case Gravity.LEFT:
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 4436fbb..f42999d 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -41,6 +41,7 @@ import android.view.ViewParent;
import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
import android.widget.RemoteViews.RemoteView;
import java.util.ArrayList;
@@ -1550,6 +1551,32 @@ public class ListView extends AbsListView {
setSelectedPositionInt(mNextSelectedPosition);
+ // Remember which child, if any, had accessibility focus. This must
+ // occur before recycling any views, since that will clear
+ // accessibility focus.
+ 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);
+ }
+ }
+ }
+ }
+
// Pull all children into the RecycleBin.
// These views will be reused if possible
final int firstPosition = mFirstPosition;
@@ -1590,30 +1617,6 @@ 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();
@@ -1713,11 +1716,17 @@ 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();
+ if (accessibilityFocusLayoutRestoreView != null) {
+ final AccessibilityNodeProvider provider =
+ accessibilityFocusLayoutRestoreView.getAccessibilityNodeProvider();
+ if ((accessibilityFocusLayoutRestoreNode != null) && (provider != null)) {
+ final int virtualViewId = AccessibilityNodeInfo.getVirtualDescendantId(
+ accessibilityFocusLayoutRestoreNode.getSourceNodeId());
+ provider.performAction(virtualViewId,
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ } else {
+ accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
+ }
} else if (accessibilityFocusPosition != INVALID_POSITION) {
// Bound the position within the visible children.
final int position = MathUtils.constrain(
@@ -2415,6 +2424,34 @@ public class ListView extends AbsListView {
}
/**
+ * Used by {@link #arrowScrollImpl(int)} to help determine the next selected position
+ * to move to. This can return a position currently not represented by a view on screen
+ * but only in the direction given.
+ *
+ * @param selectedPos Current selected position to move from
+ * @param direction Direction to move in
+ * @return Desired selected position after moving in the given direction
+ */
+ private final int nextSelectedPositionForDirection(int selectedPos, int direction) {
+ int nextSelected;
+ if (direction == View.FOCUS_DOWN) {
+ nextSelected = selectedPos != INVALID_POSITION && selectedPos >= mFirstPosition ?
+ selectedPos + 1 :
+ mFirstPosition;
+ } else {
+ final int lastPos = mFirstPosition + getChildCount() - 1;
+ nextSelected = selectedPos != INVALID_POSITION && selectedPos <= lastPos ?
+ selectedPos - 1 :
+ lastPos;
+ }
+
+ if (nextSelected < 0 || nextSelected >= mAdapter.getCount()) {
+ return INVALID_POSITION;
+ }
+ return lookForSelectablePosition(nextSelected, direction == View.FOCUS_DOWN);
+ }
+
+ /**
* Handle an arrow scroll going up or down. Take into account whether items are selectable,
* whether there are focusable items etc.
*
@@ -2429,9 +2466,7 @@ public class ListView extends AbsListView {
View selectedView = getSelectedView();
int selectedPos = mSelectedPosition;
- int nextSelectedPosition = (direction == View.FOCUS_DOWN) ?
- lookForSelectablePosition(selectedPos + 1, true) :
- lookForSelectablePosition(selectedPos - 1, false);
+ int nextSelectedPosition = nextSelectedPositionForDirection(selectedPos, direction);
int amountToScroll = amountToScroll(direction, nextSelectedPosition);
// if we are moving focus, we may OVERRIDE the default behavior
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index f76ab2b..9c61fd6 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -149,7 +149,7 @@ public class MediaController extends FrameLayout {
private void initFloatingWindowLayout() {
mDecorLayoutParams = new WindowManager.LayoutParams();
WindowManager.LayoutParams p = mDecorLayoutParams;
- p.gravity = Gravity.TOP;
+ p.gravity = Gravity.TOP | Gravity.LEFT;
p.height = LayoutParams.WRAP_CONTENT;
p.x = 0;
p.format = PixelFormat.TRANSLUCENT;
@@ -167,9 +167,15 @@ public class MediaController extends FrameLayout {
int [] anchorPos = new int[2];
mAnchor.getLocationOnScreen(anchorPos);
+ // we need to know the size of the controller so we can properly position it
+ // within its space
+ mDecor.measure(MeasureSpec.makeMeasureSpec(mAnchor.getWidth(), MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(mAnchor.getHeight(), MeasureSpec.AT_MOST));
+
WindowManager.LayoutParams p = mDecorLayoutParams;
p.width = mAnchor.getWidth();
- p.y = anchorPos[1] + mAnchor.getHeight();
+ p.x = anchorPos[0] + (mAnchor.getWidth() - p.width) / 2;
+ p.y = anchorPos[1] + mAnchor.getHeight() - mDecor.getMeasuredHeight();
}
// This is called whenever mAnchor's layout bound changes
@@ -204,6 +210,8 @@ public class MediaController extends FrameLayout {
/**
* Set the view that acts as the anchor for the control view.
* This can for example be a VideoView, or your Activity's main view.
+ * When VideoView calls this method, it will use the VideoView's parent
+ * as the anchor.
* @param view The view to which to anchor the controller when it is visible.
*/
public void setAnchorView(View view) {
@@ -669,5 +677,12 @@ public class MediaController extends FrameLayout {
boolean canPause();
boolean canSeekBackward();
boolean canSeekForward();
+
+ /**
+ * Get the audio session id for the player used by this VideoView. This can be used to
+ * apply audio effects to the audio track of a video.
+ * @return The audio session, or 0 if there was an error.
+ */
+ int getAudioSessionId();
}
}
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 2ac5a12..4a98f66 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -2185,7 +2185,10 @@ public class NumberPicker extends LinearLayout {
mScrollX + (mRight - mLeft),
mTopSelectionDividerTop + mSelectionDividerHeight);
case VIRTUAL_VIEW_ID_INPUT:
- return createAccessibiltyNodeInfoForInputText();
+ return createAccessibiltyNodeInfoForInputText(mScrollX,
+ mTopSelectionDividerTop + mSelectionDividerHeight,
+ mScrollX + (mRight - mLeft),
+ mBottomSelectionDividerBottom - mSelectionDividerHeight);
case VIRTUAL_VIEW_ID_INCREMENT:
return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_INCREMENT,
getVirtualIncrementButtonText(), mScrollX,
@@ -2446,7 +2449,8 @@ public class NumberPicker extends LinearLayout {
}
}
- private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText() {
+ private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText(
+ int left, int top, int right, int bottom) {
AccessibilityNodeInfo info = mInputText.createAccessibilityNodeInfo();
info.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
if (mAccessibilityFocusedView != VIRTUAL_VIEW_ID_INPUT) {
@@ -2455,6 +2459,15 @@ public class NumberPicker extends LinearLayout {
if (mAccessibilityFocusedView == VIRTUAL_VIEW_ID_INPUT) {
info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
}
+ Rect boundsInParent = mTempRect;
+ boundsInParent.set(left, top, right, bottom);
+ info.setVisibleToUser(isVisibleToUser(boundsInParent));
+ info.setBoundsInParent(boundsInParent);
+ Rect boundsInScreen = boundsInParent;
+ int[] locationOnScreen = mTempArray;
+ getLocationOnScreen(locationOnScreen);
+ boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
+ info.setBoundsInScreen(boundsInScreen);
return info;
}
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index ea50e2e..5392a96 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -38,10 +38,7 @@ import android.graphics.drawable.shapes.Shape;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
-import android.util.Pool;
-import android.util.Poolable;
-import android.util.PoolableManager;
-import android.util.Pools;
+import android.util.Pools.SynchronizedPool;
import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -187,6 +184,7 @@ import java.util.ArrayList;
* @attr ref android.R.styleable#ProgressBar_maxWidth
* @attr ref android.R.styleable#ProgressBar_minHeight
* @attr ref android.R.styleable#ProgressBar_minWidth
+ * @attr ref android.R.styleable#ProgressBar_mirrorForRtl
* @attr ref android.R.styleable#ProgressBar_progress
* @attr ref android.R.styleable#ProgressBar_progressDrawable
* @attr ref android.R.styleable#ProgressBar_secondaryProgress
@@ -226,6 +224,8 @@ public class ProgressBar extends View {
private boolean mAttached;
private boolean mRefreshIsPosted;
+ boolean mMirrorForRtl = false;
+
private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>();
private AccessibilityEventSender mAccessibilityEventSender;
@@ -305,6 +305,8 @@ public class ProgressBar extends View {
setIndeterminate(mOnlyIndeterminate || a.getBoolean(
R.styleable.ProgressBar_indeterminate, mIndeterminate));
+ mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl);
+
a.recycle();
}
@@ -604,33 +606,20 @@ public class ProgressBar extends View {
}
}
- private static class RefreshData implements Poolable<RefreshData> {
+ private static class RefreshData {
+ private static final int POOL_MAX = 24;
+ private static final SynchronizedPool<RefreshData> sPool =
+ new SynchronizedPool<RefreshData>(POOL_MAX);
+
public int id;
public int progress;
public boolean fromUser;
-
- private RefreshData mNext;
- private boolean mIsPooled;
-
- private static final int POOL_MAX = 24;
- private static final Pool<RefreshData> sPool = Pools.synchronizedPool(
- Pools.finitePool(new PoolableManager<RefreshData>() {
- @Override
- public RefreshData newInstance() {
- return new RefreshData();
- }
-
- @Override
- public void onAcquired(RefreshData element) {
- }
-
- @Override
- public void onReleased(RefreshData element) {
- }
- }, POOL_MAX));
public static RefreshData obtain(int id, int progress, boolean fromUser) {
RefreshData rd = sPool.acquire();
+ if (rd == null) {
+ rd = new RefreshData();
+ }
rd.id = id;
rd.progress = progress;
rd.fromUser = fromUser;
@@ -640,28 +629,8 @@ public class ProgressBar extends View {
public void recycle() {
sPool.release(this);
}
-
- @Override
- public void setNextPoolable(RefreshData element) {
- mNext = element;
- }
-
- @Override
- public RefreshData getNextPoolable() {
- return mNext;
- }
-
- @Override
- public boolean isPooled() {
- return mIsPooled;
- }
-
- @Override
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
}
-
+
private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
boolean callBackToApp) {
float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
@@ -1040,7 +1009,7 @@ public class ProgressBar extends View {
}
}
}
- if (isLayoutRtl()) {
+ if (isLayoutRtl() && mMirrorForRtl) {
int tempLeft = left;
left = w - right;
right = w - tempLeft;
@@ -1062,7 +1031,7 @@ public class ProgressBar extends View {
// Translate canvas so a indeterminate circular progress bar with padding
// rotates properly in its animation
canvas.save();
- if(isLayoutRtl()) {
+ if(isLayoutRtl() && mMirrorForRtl) {
canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
canvas.scale(-1.0f, 1.0f);
} else {
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 786afe2..368f6ad 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -27,6 +27,7 @@ import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
@@ -50,6 +51,7 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
private Drawable mOverlay;
private QueryHandler mQueryHandler;
private Drawable mDefaultAvatar;
+ private Bundle mExtras = null;
protected String[] mExcludeMimes = null;
@@ -58,6 +60,8 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
static final private int TOKEN_EMAIL_LOOKUP_AND_TRIGGER = 2;
static final private int TOKEN_PHONE_LOOKUP_AND_TRIGGER = 3;
+ static final private String EXTRA_URI_CONTENT = "uri_content";
+
static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
RawContacts.CONTACT_ID,
Contacts.LOOKUP_KEY,
@@ -175,7 +179,26 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
* until this view is clicked.
*/
public void assignContactFromEmail(String emailAddress, boolean lazyLookup) {
+ assignContactFromEmail(emailAddress, lazyLookup, null);
+ }
+
+ /**
+ * Assign a contact based on an email address. This should only be used when
+ * the contact's URI is not available, as an extra query will have to be
+ * performed to lookup the URI based on the email.
+
+ @param emailAddress The email address of the contact.
+ @param lazyLookup If this is true, the lookup query will not be performed
+ until this view is clicked.
+ @param extras A bundle of extras to populate the contact edit page with if the contact
+ is not found and the user chooses to add the email address to an existing contact or
+ create a new contact. Uses the same string constants as those found in
+ {@link android.provider.ContactsContract.Intents.Insert}
+ */
+
+ public void assignContactFromEmail(String emailAddress, boolean lazyLookup, Bundle extras) {
mContactEmail = emailAddress;
+ mExtras = extras;
if (!lazyLookup) {
mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, null,
Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
@@ -186,6 +209,7 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
}
}
+
/**
* Assign a contact based on a phone number. This should only be used when
* the contact's URI is not available, as an extra query will have to be
@@ -196,7 +220,25 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
* until this view is clicked.
*/
public void assignContactFromPhone(String phoneNumber, boolean lazyLookup) {
+ assignContactFromPhone(phoneNumber, lazyLookup, new Bundle());
+ }
+
+ /**
+ * Assign a contact based on a phone number. This should only be used when
+ * the contact's URI is not available, as an extra query will have to be
+ * performed to lookup the URI based on the phone number.
+ *
+ * @param phoneNumber The phone number of the contact.
+ * @param lazyLookup If this is true, the lookup query will not be performed
+ * until this view is clicked.
+ * @param extras A bundle of extras to populate the contact edit page with if the contact
+ * is not found and the user chooses to add the phone number to an existing contact or
+ * create a new contact. Uses the same string constants as those found in
+ * {@link android.provider.ContactsContract.Intents.Insert}
+ */
+ public void assignContactFromPhone(String phoneNumber, boolean lazyLookup, Bundle extras) {
mContactPhone = phoneNumber;
+ mExtras = extras;
if (!lazyLookup) {
mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, null,
Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone),
@@ -213,15 +255,21 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
@Override
public void onClick(View v) {
+ // If contact has been assigned, mExtras should no longer be null, but do a null check
+ // anyway just in case assignContactFromPhone or Email was called with a null bundle or
+ // wasn't assigned previously.
+ final Bundle extras = (mExtras == null) ? new Bundle() : mExtras;
if (mContactUri != null) {
QuickContact.showQuickContact(getContext(), QuickContactBadge.this, mContactUri,
QuickContact.MODE_LARGE, mExcludeMimes);
} else if (mContactEmail != null) {
- mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, mContactEmail,
+ extras.putString(EXTRA_URI_CONTENT, mContactEmail);
+ mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, extras,
Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
EMAIL_LOOKUP_PROJECTION, null, null, null);
} else if (mContactPhone != null) {
- mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP_AND_TRIGGER, mContactPhone,
+ extras.putString(EXTRA_URI_CONTENT, mContactPhone);
+ mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP_AND_TRIGGER, extras,
Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone),
PHONE_LOOKUP_PROJECTION, null, null, null);
} else {
@@ -262,12 +310,12 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
Uri lookupUri = null;
Uri createUri = null;
boolean trigger = false;
-
+ Bundle extras = (cookie != null) ? (Bundle) cookie : new Bundle();
try {
switch(token) {
case TOKEN_PHONE_LOOKUP_AND_TRIGGER:
trigger = true;
- createUri = Uri.fromParts("tel", (String)cookie, null);
+ createUri = Uri.fromParts("tel", extras.getString(EXTRA_URI_CONTENT), null);
//$FALL-THROUGH$
case TOKEN_PHONE_LOOKUP: {
@@ -281,7 +329,8 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
}
case TOKEN_EMAIL_LOOKUP_AND_TRIGGER:
trigger = true;
- createUri = Uri.fromParts("mailto", (String)cookie, null);
+ createUri = Uri.fromParts("mailto",
+ extras.getString(EXTRA_URI_CONTENT), null);
//$FALL-THROUGH$
case TOKEN_EMAIL_LOOKUP: {
@@ -309,6 +358,10 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
} else if (createUri != null) {
// Prompt user to add this person to contacts
final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, createUri);
+ if (extras != null) {
+ extras.remove(EXTRA_URI_CONTENT);
+ intent.putExtras(extras);
+ }
getContext().startActivity(intent);
}
}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 27fda24..f940226 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -29,11 +29,9 @@ import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
+import android.os.Build;
import android.util.AttributeSet;
-import android.util.Pool;
-import android.util.Poolable;
-import android.util.PoolableManager;
-import android.util.Pools;
+import android.util.Pools.SimplePool;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.View;
@@ -43,6 +41,7 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.util.Log.d;
/**
@@ -56,6 +55,21 @@ import static android.util.Log.d;
* {@link #ALIGN_PARENT_BOTTOM}.
* </p>
*
+ * <p><strong>Note:</strong> In platform version 17 and lower, RelativeLayout was affected by
+ * a measurement bug that could cause child views to be measured with incorrect
+ * {@link android.view.View.MeasureSpec MeasureSpec} values. (See
+ * {@link android.view.View.MeasureSpec#makeMeasureSpec(int, int) MeasureSpec.makeMeasureSpec}
+ * for more details.) This was triggered when a RelativeLayout container was placed in
+ * a scrolling container, such as a ScrollView or HorizontalScrollView. If a custom view
+ * not equipped to properly measure with the MeasureSpec mode
+ * {@link android.view.View.MeasureSpec#UNSPECIFIED UNSPECIFIED} was placed in a RelativeLayout,
+ * this would silently work anyway as RelativeLayout would pass a very large
+ * {@link android.view.View.MeasureSpec#AT_MOST AT_MOST} MeasureSpec instead.</p>
+ *
+ * <p>This behavior has been preserved for apps that set <code>android:targetSdkVersion="17"</code>
+ * or older in their manifest's <code>uses-sdk</code> tag for compatibility. Apps targeting SDK
+ * version 18 or newer will receive the correct behavior</p>
+ *
* <p>See the <a href="{@docRoot}guide/topics/ui/layout/relative.html">Relative
* Layout</a> guide.</p>
*
@@ -202,18 +216,38 @@ public class RelativeLayout extends ViewGroup {
private View[] mSortedVerticalChildren = new View[0];
private final DependencyGraph mGraph = new DependencyGraph();
+ // Compatibility hack. Old versions of the platform had problems
+ // with MeasureSpec value overflow and RelativeLayout was one source of them.
+ // Some apps came to rely on them. :(
+ private boolean mAllowBrokenMeasureSpecs = false;
+ // Compatibility hack. Old versions of the platform would not take
+ // margins and padding into account when generating the height measure spec
+ // for children during the horizontal measure pass.
+ private boolean mMeasureVerticalWithPaddingMargin = false;
+
+ // A default width used for RTL measure pass
+ /**
+ * Value reduced so as not to interfere with View's measurement spec. flags. See:
+ * {@link View#MEASURED_SIZE_MASK}.
+ * {@link View#MEASURED_STATE_TOO_SMALL}.
+ **/
+ private static final int DEFAULT_WIDTH = 0x00010000;
+
public RelativeLayout(Context context) {
super(context);
+ queryCompatibilityModes(context);
}
public RelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initFromAttributes(context, attrs);
+ queryCompatibilityModes(context);
}
public RelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initFromAttributes(context, attrs);
+ queryCompatibilityModes(context);
}
private void initFromAttributes(Context context, AttributeSet attrs) {
@@ -223,6 +257,12 @@ public class RelativeLayout extends ViewGroup {
a.recycle();
}
+ private void queryCompatibilityModes(Context context) {
+ int version = context.getApplicationInfo().targetSdkVersion;
+ mAllowBrokenMeasureSpecs = version <= Build.VERSION_CODES.JELLY_BEAN_MR1;
+ mMeasureVerticalWithPaddingMargin = version >= Build.VERSION_CODES.JELLY_BEAN_MR2;
+ }
+
@Override
public boolean shouldDelayChildPressedState() {
return false;
@@ -414,51 +454,28 @@ public class RelativeLayout extends ViewGroup {
final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;
+ // We need to know our size for doing the correct computation of children positioning in RTL
+ // mode but there is no practical way to get it instead of running the code below.
+ // So, instead of running the code twice, we just set the width to a "default display width"
+ // before the computation and then, as a last pass, we will update their real position with
+ // an offset equals to "DEFAULT_WIDTH - width".
+ final int layoutDirection = getLayoutDirection();
+ if (isLayoutRtl() && myWidth == -1) {
+ myWidth = DEFAULT_WIDTH;
+ }
+
View[] views = mSortedHorizontalChildren;
int count = views.length;
- // We need to know our size for doing the correct computation of positioning in RTL mode
- if (isLayoutRtl() && (myWidth == -1 || isWrapContentWidth)) {
- int w = getPaddingStart() + getPaddingEnd();
- final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- for (int i = 0; i < count; i++) {
- View child = views[i];
- if (child.getVisibility() != GONE) {
- LayoutParams params = (LayoutParams) child.getLayoutParams();
- // Would be similar to a call to measureChildHorizontal(child, params, -1, myHeight)
- // but we cannot change for now the behavior of measureChildHorizontal() for
- // taking care or a "-1" for "mywidth" so use here our own version of that code.
- int childHeightMeasureSpec;
- if (params.width == LayoutParams.MATCH_PARENT) {
- childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY);
- } else {
- childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST);
- }
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
-
- w += child.getMeasuredWidth();
- w += params.leftMargin + params.rightMargin;
- }
- }
- if (myWidth == -1) {
- // Easy case: "myWidth" was undefined before so use the width we have just computed
- myWidth = w;
- } else {
- // "myWidth" was defined before, so take the min of it and the computed width if it
- // is a non null one
- if (w > 0) {
- myWidth = Math.min(myWidth, w);
- }
- }
- }
-
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
+ int[] rules = params.getRules(layoutDirection);
- applyHorizontalSizeRules(params, myWidth);
+ applyHorizontalSizeRules(params, myWidth, rules);
measureChildHorizontal(child, params, myWidth, myHeight);
+
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true;
}
@@ -480,7 +497,11 @@ public class RelativeLayout extends ViewGroup {
}
if (isWrapContentWidth) {
- width = Math.max(width, params.mRight);
+ if (isLayoutRtl()) {
+ width = Math.max(width, myWidth - params.mLeft);
+ } else {
+ width = Math.max(width, params.mRight);
+ }
}
if (isWrapContentHeight) {
@@ -519,8 +540,6 @@ 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
@@ -610,6 +629,19 @@ public class RelativeLayout extends ViewGroup {
}
}
+ if (isLayoutRtl()) {
+ final int offsetWidth = myWidth - width;
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ LayoutParams params = (LayoutParams) child.getLayoutParams();
+ params.mLeft -= offsetWidth;
+ params.mRight -= offsetWidth;
+ }
+ }
+
+ }
+
setMeasuredDimension(width, height);
}
@@ -672,11 +704,26 @@ public class RelativeLayout extends ViewGroup {
params.leftMargin, params.rightMargin,
mPaddingLeft, mPaddingRight,
myWidth);
+ int maxHeight = myHeight;
+ if (mMeasureVerticalWithPaddingMargin) {
+ maxHeight = Math.max(0, myHeight - mPaddingTop - mPaddingBottom -
+ params.topMargin - params.bottomMargin);
+ }
int childHeightMeasureSpec;
- if (params.width == LayoutParams.MATCH_PARENT) {
- childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY);
+ if (myHeight < 0 && !mAllowBrokenMeasureSpecs) {
+ if (params.height >= 0) {
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ params.height, MeasureSpec.EXACTLY);
+ } else {
+ // Negative values in a mySize/myWidth/myWidth value in RelativeLayout measurement
+ // is code for, "we got an unspecified mode in the RelativeLayout's measurespec."
+ // Carry it forward.
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+ } else if (params.width == LayoutParams.MATCH_PARENT) {
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
} else {
- childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST);
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@@ -700,6 +747,16 @@ public class RelativeLayout extends ViewGroup {
private int getChildMeasureSpec(int childStart, int childEnd,
int childSize, int startMargin, int endMargin, int startPadding,
int endPadding, int mySize) {
+ if (mySize < 0 && !mAllowBrokenMeasureSpecs) {
+ if (childSize >= 0) {
+ return MeasureSpec.makeMeasureSpec(childSize, MeasureSpec.EXACTLY);
+ }
+ // Negative values in a mySize/myWidth/myWidth value in RelativeLayout measurement
+ // is code for, "we got an unspecified mode in the RelativeLayout's measurespec."
+ // Carry it forward.
+ return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+
int childSpecMode = 0;
int childSpecSize = 0;
@@ -826,9 +883,7 @@ public class RelativeLayout extends ViewGroup {
return rules[ALIGN_PARENT_BOTTOM] != 0;
}
- private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) {
- final int layoutDirection = getLayoutDirection();
- int[] rules = childParams.getRules(layoutDirection);
+ private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
RelativeLayout.LayoutParams anchorParams;
// -1 indicated a "soft requirement" in that direction. For example:
@@ -991,7 +1046,7 @@ public class RelativeLayout extends ViewGroup {
return -1;
}
- private void centerHorizontal(View child, LayoutParams params, int myWidth) {
+ private static void centerHorizontal(View child, LayoutParams params, int myWidth) {
int childWidth = child.getMeasuredWidth();
int left = (myWidth - childWidth) / 2;
@@ -999,7 +1054,7 @@ public class RelativeLayout extends ViewGroup {
params.mRight = left + childWidth;
}
- private void centerVertical(View child, LayoutParams params, int myHeight) {
+ private static void centerVertical(View child, LayoutParams params, int myHeight) {
int childHeight = child.getMeasuredHeight();
int top = (myHeight - childHeight) / 2;
@@ -1174,10 +1229,11 @@ public class RelativeLayout extends ViewGroup {
private int mLeft, mTop, mRight, mBottom;
- private int mStart = DEFAULT_RELATIVE;
- private int mEnd = DEFAULT_RELATIVE;
+ private int mStart = DEFAULT_MARGIN_RELATIVE;
+ private int mEnd = DEFAULT_MARGIN_RELATIVE;
private boolean mRulesChanged = false;
+ private boolean mIsRtlCompatibilityMode = false;
/**
* When true, uses the parent as the anchor if the anchor doesn't exist or if
@@ -1192,7 +1248,12 @@ public class RelativeLayout extends ViewGroup {
TypedArray a = c.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.RelativeLayout_Layout);
+ final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
+ mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
+ !c.getApplicationInfo().hasRtlSupport());
+
final int[] rules = mRules;
+ //noinspection MismatchedReadAndWriteOfArray
final int[] initialRules = mInitialRules;
final int N = a.getIndexCount();
@@ -1270,10 +1331,8 @@ public class RelativeLayout extends ViewGroup {
break;
}
}
-
- for (int n = LEFT_OF; n < VERB_COUNT; n++) {
- initialRules[n] = rules[n];
- }
+ mRulesChanged = true;
+ System.arraycopy(rules, LEFT_OF, initialRules, LEFT_OF, VERB_COUNT);
a.recycle();
}
@@ -1361,30 +1420,132 @@ public class RelativeLayout extends ViewGroup {
mInitialRules[ALIGN_PARENT_START] != 0 || mInitialRules[ALIGN_PARENT_END] != 0);
}
+ // The way we are resolving rules depends on the layout direction and if we are pre JB MR1
+ // or not.
+ //
+ // If we are pre JB MR1 (said as "RTL compatibility mode"), "left"/"right" rules are having
+ // predominance over any "start/end" rules that could have been defined. A special case:
+ // if no "left"/"right" rule has been defined and "start"/"end" rules are defined then we
+ // resolve those "start"/"end" rules to "left"/"right" respectively.
+ //
+ // If we are JB MR1+, then "start"/"end" rules are having predominance over "left"/"right"
+ // rules. If no "start"/"end" rule is defined then we use "left"/"right" rules.
+ //
+ // In all cases, the result of the resolution should clear the "start"/"end" rules to leave
+ // only the "left"/"right" rules at the end.
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];
+ System.arraycopy(mInitialRules, LEFT_OF, mRules, LEFT_OF, VERB_COUNT);
+
+ // Apply rules depending on direction and if we are in RTL compatibility mode
+ if (mIsRtlCompatibilityMode) {
+ if (mRules[ALIGN_START] != 0) {
+ if (mRules[ALIGN_LEFT] == 0) {
+ // "left" rule is not defined but "start" rule is: use the "start" rule as
+ // the "left" rule
+ mRules[ALIGN_LEFT] = mRules[ALIGN_START];
+ }
+ mRules[ALIGN_START] = 0;
+ }
+
+ if (mRules[ALIGN_END] != 0) {
+ if (mRules[ALIGN_RIGHT] == 0) {
+ // "right" rule is not defined but "end" rule is: use the "end" rule as the
+ // "right" rule
+ mRules[ALIGN_RIGHT] = mRules[ALIGN_END];
+ }
+ mRules[ALIGN_END] = 0;
+ }
+
+ if (mRules[START_OF] != 0) {
+ if (mRules[LEFT_OF] == 0) {
+ // "left" rule is not defined but "start" rule is: use the "start" rule as
+ // the "left" rule
+ mRules[LEFT_OF] = mRules[START_OF];
+ }
+ mRules[START_OF] = 0;
+ }
+
+ if (mRules[END_OF] != 0) {
+ if (mRules[RIGHT_OF] == 0) {
+ // "right" rule is not defined but "end" rule is: use the "end" rule as the
+ // "right" rule
+ mRules[RIGHT_OF] = mRules[END_OF];
+ }
+ mRules[END_OF] = 0;
+ }
+
+ if (mRules[ALIGN_PARENT_START] != 0) {
+ if (mRules[ALIGN_PARENT_LEFT] == 0) {
+ // "left" rule is not defined but "start" rule is: use the "start" rule as
+ // the "left" rule
+ mRules[ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START];
+ }
+ mRules[ALIGN_PARENT_START] = 0;
+ }
+
+ if (mRules[ALIGN_PARENT_RIGHT] == 0) {
+ if (mRules[ALIGN_PARENT_RIGHT] == 0) {
+ // "right" rule is not defined but "end" rule is: use the "end" rule as the
+ // "right" rule
+ mRules[ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END];
+ }
+ mRules[ALIGN_PARENT_END] = 0;
+ }
+ } else {
+ // JB MR1+ case
+ if ((mRules[ALIGN_START] != 0 || mRules[ALIGN_END] != 0) &&
+ (mRules[ALIGN_LEFT] != 0 || mRules[ALIGN_RIGHT] != 0)) {
+ // "start"/"end" rules take precedence over "left"/"right" rules
+ mRules[ALIGN_LEFT] = 0;
+ mRules[ALIGN_RIGHT] = 0;
+ }
+ if (mRules[ALIGN_START] != 0) {
+ // "start" rule resolved to "left" or "right" depending on the direction
+ mRules[isLayoutRtl ? ALIGN_RIGHT : ALIGN_LEFT] = mRules[ALIGN_START];
+ mRules[ALIGN_START] = 0;
+ }
+ if (mRules[ALIGN_END] != 0) {
+ // "end" rule resolved to "left" or "right" depending on the direction
+ mRules[isLayoutRtl ? ALIGN_LEFT : ALIGN_RIGHT] = mRules[ALIGN_END];
+ mRules[ALIGN_END] = 0;
+ }
+
+ if ((mRules[START_OF] != 0 || mRules[END_OF] != 0) &&
+ (mRules[LEFT_OF] != 0 || mRules[RIGHT_OF] != 0)) {
+ // "start"/"end" rules take precedence over "left"/"right" rules
+ mRules[LEFT_OF] = 0;
+ mRules[RIGHT_OF] = 0;
+ }
+ if (mRules[START_OF] != 0) {
+ // "start" rule resolved to "left" or "right" depending on the direction
+ mRules[isLayoutRtl ? RIGHT_OF : LEFT_OF] = mRules[START_OF];
+ mRules[START_OF] = 0;
+ }
+ if (mRules[END_OF] != 0) {
+ // "end" rule resolved to "left" or "right" depending on the direction
+ mRules[isLayoutRtl ? LEFT_OF : RIGHT_OF] = mRules[END_OF];
+ mRules[END_OF] = 0;
+ }
+
+ if ((mRules[ALIGN_PARENT_START] != 0 || mRules[ALIGN_PARENT_END] != 0) &&
+ (mRules[ALIGN_PARENT_LEFT] != 0 || mRules[ALIGN_PARENT_RIGHT] != 0)) {
+ // "start"/"end" rules take precedence over "left"/"right" rules
+ mRules[ALIGN_PARENT_LEFT] = 0;
+ mRules[ALIGN_PARENT_RIGHT] = 0;
+ }
+ if (mRules[ALIGN_PARENT_START] != 0) {
+ // "start" rule resolved to "left" or "right" depending on the direction
+ mRules[isLayoutRtl ? ALIGN_PARENT_RIGHT : ALIGN_PARENT_LEFT] = mRules[ALIGN_PARENT_START];
+ mRules[ALIGN_PARENT_START] = 0;
+ }
+ if (mRules[ALIGN_PARENT_END] != 0) {
+ // "end" rule resolved to "left" or "right" depending on the direction
+ mRules[isLayoutRtl ? ALIGN_PARENT_LEFT : ALIGN_PARENT_RIGHT] = mRules[ALIGN_PARENT_END];
+ mRules[ALIGN_PARENT_END] = 0;
+ }
}
mRulesChanged = false;
}
@@ -1430,11 +1591,11 @@ public class RelativeLayout extends ViewGroup {
public void resolveLayoutDirection(int layoutDirection) {
final boolean isLayoutRtl = isLayoutRtl();
if (isLayoutRtl) {
- if (mStart != DEFAULT_RELATIVE) mRight = mStart;
- if (mEnd != DEFAULT_RELATIVE) mLeft = mEnd;
+ if (mStart != DEFAULT_MARGIN_RELATIVE) mRight = mStart;
+ if (mEnd != DEFAULT_MARGIN_RELATIVE) mLeft = mEnd;
} else {
- if (mStart != DEFAULT_RELATIVE) mLeft = mStart;
- if (mEnd != DEFAULT_RELATIVE) mRight = mEnd;
+ if (mStart != DEFAULT_MARGIN_RELATIVE) mLeft = mStart;
+ if (mEnd != DEFAULT_MARGIN_RELATIVE) mRight = mEnd;
}
if (hasRelativeRules() && layoutDirection != getLayoutDirection()) {
@@ -1655,7 +1816,7 @@ public class RelativeLayout extends ViewGroup {
*
* A node with no dependent is considered a root of the graph.
*/
- static class Node implements Poolable<Node> {
+ static class Node {
/**
* The view representing this node in the layout.
*/
@@ -1678,43 +1839,14 @@ public class RelativeLayout extends ViewGroup {
// The pool is static, so all nodes instances are shared across
// activities, that's why we give it a rather high limit
private static final int POOL_LIMIT = 100;
- private static final Pool<Node> sPool = Pools.synchronizedPool(
- Pools.finitePool(new PoolableManager<Node>() {
- public Node newInstance() {
- return new Node();
- }
-
- public void onAcquired(Node element) {
- }
-
- public void onReleased(Node element) {
- }
- }, POOL_LIMIT)
- );
-
- private Node mNext;
- private boolean mIsPooled;
-
- public void setNextPoolable(Node element) {
- mNext = element;
- }
-
- public Node getNextPoolable() {
- return mNext;
- }
-
- public boolean isPooled() {
- return mIsPooled;
- }
-
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
+ private static final SimplePool<Node> sPool = new SimplePool<Node>(POOL_LIMIT);
static Node acquire(View view) {
- final Node node = sPool.acquire();
+ Node node = sPool.acquire();
+ if (node == null) {
+ node = new Node();
+ }
node.view = view;
-
return node;
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 8d1be53..07e66b7 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -34,6 +34,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.StrictMode;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -53,7 +54,6 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
-
/**
* A class that describes a view hierarchy that can be displayed in
* another process. The hierarchy is inflated from a layout resource
@@ -340,7 +340,7 @@ public class RemoteViews implements Parcelable, Filter {
if (target == null) return;
if (!mIsWidgetCollectionChild) {
- Log.e("RemoteViews", "The method setOnClickFillInIntent is available " +
+ Log.e(LOG_TAG, "The method setOnClickFillInIntent is available " +
"only from RemoteViewsFactory (ie. on collection items).");
return;
}
@@ -359,13 +359,13 @@ public class RemoteViews implements Parcelable, Filter {
if (parent instanceof AppWidgetHostView || parent == null) {
// Somehow they've managed to get this far without having
// and AdapterView as a parent.
- Log.e("RemoteViews", "Collection item doesn't have AdapterView parent");
+ Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent");
return;
}
// Insure that a template pending intent has been set on an ancestor
if (!(parent.getTag() instanceof PendingIntent)) {
- Log.e("RemoteViews", "Attempting setOnClickFillInIntent without" +
+ Log.e(LOG_TAG, "Attempting setOnClickFillInIntent without" +
" calling setPendingIntentTemplate on parent.");
return;
}
@@ -472,7 +472,7 @@ public class RemoteViews implements Parcelable, Filter {
av.setOnItemClickListener(listener);
av.setTag(pendingIntentTemplate);
} else {
- Log.e("RemoteViews", "Cannot setPendingIntentTemplate on a view which is not" +
+ Log.e(LOG_TAG, "Cannot setPendingIntentTemplate on a view which is not" +
"an AdapterView (id: " + viewId + ")");
return;
}
@@ -487,6 +487,88 @@ public class RemoteViews implements Parcelable, Filter {
public final static int TAG = 8;
}
+ private class SetRemoteViewsAdapterList extends Action {
+ public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) {
+ this.viewId = id;
+ this.list = list;
+ this.viewTypeCount = viewTypeCount;
+ }
+
+ public SetRemoteViewsAdapterList(Parcel parcel) {
+ viewId = parcel.readInt();
+ viewTypeCount = parcel.readInt();
+ int count = parcel.readInt();
+ list = new ArrayList<RemoteViews>();
+
+ for (int i = 0; i < count; i++) {
+ RemoteViews rv = RemoteViews.CREATOR.createFromParcel(parcel);
+ list.add(rv);
+ }
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(TAG);
+ dest.writeInt(viewId);
+ dest.writeInt(viewTypeCount);
+
+ if (list == null || list.size() == 0) {
+ dest.writeInt(0);
+ } else {
+ int count = list.size();
+ dest.writeInt(count);
+ for (int i = 0; i < count; i++) {
+ RemoteViews rv = list.get(i);
+ rv.writeToParcel(dest, flags);
+ }
+ }
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+ final View target = root.findViewById(viewId);
+ if (target == null) return;
+
+ // Ensure that we are applying to an AppWidget root
+ if (!(rootParent instanceof AppWidgetHostView)) {
+ Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
+ "AppWidgets (root id: " + viewId + ")");
+ return;
+ }
+ // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
+ if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
+ Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
+ "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
+ return;
+ }
+
+ if (target instanceof AbsListView) {
+ AbsListView v = (AbsListView) target;
+ Adapter a = v.getAdapter();
+ if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
+ ((RemoteViewsListAdapter) a).setViewsList(list);
+ } else {
+ v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount));
+ }
+ } else if (target instanceof AdapterViewAnimator) {
+ AdapterViewAnimator v = (AdapterViewAnimator) target;
+ Adapter a = v.getAdapter();
+ if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
+ ((RemoteViewsListAdapter) a).setViewsList(list);
+ } else {
+ v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount));
+ }
+ }
+ }
+
+ public String getActionName() {
+ return "SetRemoteViewsAdapterList";
+ }
+
+ int viewTypeCount;
+ ArrayList<RemoteViews> list;
+ public final static int TAG = 15;
+ }
+
private class SetRemoteViewsAdapterIntent extends Action {
public SetRemoteViewsAdapterIntent(int id, Intent intent) {
this.viewId = id;
@@ -511,13 +593,13 @@ public class RemoteViews implements Parcelable, Filter {
// Ensure that we are applying to an AppWidget root
if (!(rootParent instanceof AppWidgetHostView)) {
- Log.e("RemoteViews", "SetRemoteViewsAdapterIntent action can only be used for " +
+ Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
"AppWidgets (root id: " + viewId + ")");
return;
}
// Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
- Log.e("RemoteViews", "Cannot setRemoteViewsAdapter on a view which is not " +
+ Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
"an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
return;
}
@@ -585,7 +667,7 @@ public class RemoteViews implements Parcelable, Filter {
// If the view is an AdapterView, setting a PendingIntent on click doesn't make much
// sense, do they mean to set a PendingIntent template for the AdapterView's children?
if (mIsWidgetCollectionChild) {
- Log.w("RemoteViews", "Cannot setOnClickPendingIntent for collection item " +
+ Log.w(LOG_TAG, "Cannot setOnClickPendingIntent for collection item " +
"(id: " + viewId + ")");
ApplicationInfo appInfo = root.getContext().getApplicationInfo();
@@ -772,7 +854,7 @@ public class RemoteViews implements Parcelable, Filter {
try {
//noinspection ConstantIfStatement
if (false) {
- Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
+ Log.d(LOG_TAG, "view: " + klass.getName() + " calling method: "
+ this.methodName + "()");
}
method.invoke(view);
@@ -945,7 +1027,7 @@ public class RemoteViews implements Parcelable, Filter {
this.type = in.readInt();
//noinspection ConstantIfStatement
if (false) {
- Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId)
+ Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId)
+ " methodName=" + this.methodName + " type=" + this.type);
}
@@ -1012,7 +1094,7 @@ public class RemoteViews implements Parcelable, Filter {
out.writeInt(this.type);
//noinspection ConstantIfStatement
if (false) {
- Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId)
+ Log.d(LOG_TAG, "write viewId=0x" + Integer.toHexString(this.viewId)
+ " methodName=" + this.methodName + " type=" + this.type);
}
@@ -1139,7 +1221,7 @@ public class RemoteViews implements Parcelable, Filter {
try {
//noinspection ConstantIfStatement
if (false) {
- Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
+ Log.d(LOG_TAG, "view: " + klass.getName() + " calling method: "
+ this.methodName + "(" + param.getName() + ") with "
+ (this.value == null ? "null" : this.value.getClass().getName()));
}
@@ -1562,6 +1644,9 @@ public class RemoteViews implements Parcelable, Filter {
case BitmapReflectionAction.TAG:
mActions.add(new BitmapReflectionAction(parcel));
break;
+ case SetRemoteViewsAdapterList.TAG:
+ mActions.add(new SetRemoteViewsAdapterList(parcel));
+ break;
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -1982,7 +2067,7 @@ public class RemoteViews implements Parcelable, Filter {
*
* @param appWidgetId The id of the app widget which contains the specified view. (This
* parameter is ignored in this deprecated method)
- * @param viewId The id of the {@link AbsListView}
+ * @param viewId The id of the {@link AdapterView}
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
* @deprecated This method has been deprecated. See
@@ -1997,7 +2082,7 @@ public class RemoteViews implements Parcelable, Filter {
* Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
* Can only be used for App Widgets.
*
- * @param viewId The id of the {@link AbsListView}
+ * @param viewId The id of the {@link AdapterView}
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
*/
@@ -2006,6 +2091,31 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
+ * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
+ * This is a simpler but less flexible approach to populating collection widgets. Its use is
+ * encouraged for most scenarios, as long as the total memory within the list of RemoteViews
+ * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link
+ * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still
+ * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}.
+ *
+ * This API is supported in the compatibility library for previous API levels, see
+ * RemoteViewsCompat.
+ *
+ * @param viewId The id of the {@link AdapterView}
+ * @param list The list of RemoteViews which will populate the view specified by viewId.
+ * @param viewTypeCount The maximum number of unique layout id's used to construct the list of
+ * RemoteViews. This count cannot change during the life-cycle of a given widget, so this
+ * parameter should account for the maximum possible number of types that may appear in the
+ * See {@link Adapter#getViewTypeCount()}.
+ *
+ * @hide
+ */
+ public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) {
+ addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount));
+ }
+
+ /**
* Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
*
* @param viewId The id of the view to change
@@ -2156,8 +2266,13 @@ 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();
+ if (value != null) {
+ // Resolve any filesystem path before sending remotely
+ value = value.getCanonicalUri();
+ if (StrictMode.vmFileUriExposureEnabled()) {
+ value.checkFileUriExposed("RemoteViews.setUri()");
+ }
+ }
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
}
diff --git a/core/java/android/widget/RemoteViewsListAdapter.java b/core/java/android/widget/RemoteViewsListAdapter.java
new file mode 100644
index 0000000..e490458
--- /dev/null
+++ b/core/java/android/widget/RemoteViewsListAdapter.java
@@ -0,0 +1,121 @@
+/*
+ * 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.widget;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class RemoteViewsListAdapter extends BaseAdapter {
+
+ private Context mContext;
+ private ArrayList<RemoteViews> mRemoteViewsList;
+ private ArrayList<Integer> mViewTypes = new ArrayList<Integer>();
+ private int mViewTypeCount;
+
+ public RemoteViewsListAdapter(Context context, ArrayList<RemoteViews> remoteViews,
+ int viewTypeCount) {
+ mContext = context;
+ mRemoteViewsList = remoteViews;
+ mViewTypeCount = viewTypeCount;
+ init();
+ }
+
+ public void setViewsList(ArrayList<RemoteViews> remoteViews) {
+ mRemoteViewsList = remoteViews;
+ init();
+ notifyDataSetChanged();
+ }
+
+ private void init() {
+ if (mRemoteViewsList == null) return;
+
+ mViewTypes.clear();
+ for (RemoteViews rv: mRemoteViewsList) {
+ if (!mViewTypes.contains(rv.getLayoutId())) {
+ mViewTypes.add(rv.getLayoutId());
+ }
+ }
+
+ if (mViewTypes.size() > mViewTypeCount || mViewTypeCount < 1) {
+ throw new RuntimeException("Invalid view type count -- view type count must be >= 1" +
+ "and must be as large as the total number of distinct view types");
+ }
+ }
+
+ @Override
+ public int getCount() {
+ if (mRemoteViewsList != null) {
+ return mRemoteViewsList.size();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (position < getCount()) {
+ RemoteViews rv = mRemoteViewsList.get(position);
+ rv.setIsWidgetCollectionChild(true);
+ View v;
+ if (convertView != null && rv != null &&
+ convertView.getId() == rv.getLayoutId()) {
+ v = convertView;
+ rv.reapply(mContext, v);
+ } else {
+ v = rv.apply(mContext, parent);
+ }
+ return v;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (position < getCount()) {
+ int layoutId = mRemoteViewsList.get(position).getLayoutId();
+ return mViewTypes.indexOf(layoutId);
+ } else {
+ return 0;
+ }
+ }
+
+ public int getViewTypeCount() {
+ return mViewTypeCount;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+}
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index cd8638d..0281602 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -1247,6 +1247,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
*/
@Override
public void onActionViewCollapsed() {
+ setQuery("", false);
clearFocus();
updateViewsVisibility(true);
mQueryTextView.setImeOptions(mCollapsedImeOptions);
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 74ea038..9e7f97e 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -109,7 +109,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
mIds = new int[size];
mSpellCheckSpans = new SpellCheckSpan[size];
- setLocale(mTextView.getTextServicesLocale());
+ setLocale(mTextView.getSpellCheckerLocale());
mCookie = hashCode();
}
@@ -120,7 +120,8 @@ public class SpellChecker implements SpellCheckerSessionListener {
mTextServicesManager = (TextServicesManager) mTextView.getContext().
getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
if (!mTextServicesManager.isSpellCheckerEnabled()
- || mTextServicesManager.getCurrentSpellCheckerSubtype(true) == null) {
+ || mCurrentLocale == null
+ || mTextServicesManager.getCurrentSpellCheckerSubtype(true) == null) {
mSpellCheckerSession = null;
} else {
mSpellCheckerSession = mTextServicesManager.newSpellCheckerSession(
@@ -146,8 +147,10 @@ public class SpellChecker implements SpellCheckerSessionListener {
resetSession();
- // Change SpellParsers' wordIterator locale
- mWordIterator = new WordIterator(locale);
+ if (locale != null) {
+ // Change SpellParsers' wordIterator locale
+ mWordIterator = new WordIterator(locale);
+ }
// This class is the listener for locale change: warn other locale-aware objects
mTextView.onLocaleChanged();
@@ -222,9 +225,9 @@ public class SpellChecker implements SpellCheckerSessionListener {
if (DBG) {
Log.d(TAG, "Start spell-checking: " + start + ", " + end);
}
- final Locale locale = mTextView.getTextServicesLocale();
+ final Locale locale = mTextView.getSpellCheckerLocale();
final boolean isSessionActive = isSessionActive();
- if (mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) {
+ if (locale == null || mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) {
setLocale(locale);
// Re-check the entire text
start = 0;
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 925864c..e3de0b9 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -25,6 +25,8 @@ import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
@@ -194,7 +196,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
break;
}
}
-
+
mGravity = a.getInt(com.android.internal.R.styleable.Spinner_gravity, Gravity.CENTER);
mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt));
@@ -492,20 +494,23 @@ public class Spinner extends AbsSpinner implements OnClickListener {
// Make selected view and position it
mFirstPosition = mSelectedPosition;
- View sel = makeAndAddView(mSelectedPosition);
- int width = sel.getMeasuredWidth();
- int selectedOffset = childrenLeft;
- 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;
- case Gravity.RIGHT:
- selectedOffset = childrenLeft + childrenWidth - width;
- break;
- }
- sel.offsetLeftAndRight(selectedOffset);
+
+ if (mAdapter != null) {
+ View sel = makeAndAddView(mSelectedPosition);
+ int width = sel.getMeasuredWidth();
+ int selectedOffset = childrenLeft;
+ 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;
+ case Gravity.RIGHT:
+ selectedOffset = childrenLeft + childrenWidth - width;
+ break;
+ }
+ sel.offsetLeftAndRight(selectedOffset);
+ }
// Flush any cached views that did not get reused above
mRecycler.clear();
@@ -606,7 +611,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
handled = true;
if (!mPopup.isShowing()) {
- mPopup.show();
+ mPopup.show(getTextDirection(), getTextAlignment());
}
}
@@ -697,6 +702,69 @@ public class Spinner extends AbsSpinner implements OnClickListener {
return width;
}
+ @Override
+ public Parcelable onSaveInstanceState() {
+ final SavedState ss = new SavedState(super.onSaveInstanceState());
+ ss.showDropdown = mPopup != null && mPopup.isShowing();
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+
+ super.onRestoreInstanceState(ss.getSuperState());
+
+ if (ss.showDropdown) {
+ ViewTreeObserver vto = getViewTreeObserver();
+ if (vto != null) {
+ final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (!mPopup.isShowing()) {
+ mPopup.show(getTextDirection(), getTextAlignment());
+ }
+ final ViewTreeObserver vto = getViewTreeObserver();
+ if (vto != null) {
+ vto.removeOnGlobalLayoutListener(this);
+ }
+ }
+ };
+ vto.addOnGlobalLayoutListener(listener);
+ }
+ }
+ }
+
+ static class SavedState extends AbsSpinner.SavedState {
+ boolean showDropdown;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ showDropdown = in.readByte() != 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeByte((byte) (showDropdown ? 1 : 0));
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
/**
* <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
* into a ListAdapter.</p>
@@ -734,8 +802,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
- return mAdapter == null ? null :
- mAdapter.getDropDownView(position, convertView, parent);
+ return (mAdapter == null) ? null : mAdapter.getDropDownView(position, convertView, parent);
}
public boolean hasStableIds() {
@@ -803,8 +870,8 @@ public class Spinner extends AbsSpinner implements OnClickListener {
/**
* Show the popup
*/
- public void show();
-
+ public void show(int textDirection, int textAlignment);
+
/**
* Dismiss the popup
*/
@@ -857,13 +924,17 @@ public class Spinner extends AbsSpinner implements OnClickListener {
return mPrompt;
}
- public void show() {
+ public void show(int textDirection, int textAlignment) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
if (mPrompt != null) {
builder.setTitle(mPrompt);
}
mPopup = builder.setSingleChoiceItems(mListAdapter,
- getSelectedItemPosition(), this).show();
+ getSelectedItemPosition(), this).create();
+ final ListView listView = mPopup.getListView();
+ listView.setTextDirection(textDirection);
+ listView.setTextAlignment(textAlignment);
+ mPopup.show();
}
public void onClick(DialogInterface dialog, int which) {
@@ -941,8 +1012,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
mHintText = hintText;
}
- @Override
- public void show() {
+ void computeContentWidth() {
final Drawable background = getBackground();
int hOffset = 0;
if (background != null) {
@@ -955,6 +1025,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
final int spinnerPaddingRight = Spinner.this.getPaddingRight();
final int spinnerWidth = Spinner.this.getWidth();
+
if (mDropDownWidth == WRAP_CONTENT) {
int contentWidth = measureContentWidth(
(SpinnerAdapter) mAdapter, getBackground());
@@ -977,11 +1048,27 @@ public class Spinner extends AbsSpinner implements OnClickListener {
hOffset += spinnerPaddingLeft;
}
setHorizontalOffset(hOffset);
+ }
+
+ public void show(int textDirection, int textAlignment) {
+ final boolean wasShowing = isShowing();
+
+ computeContentWidth();
+
setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
super.show();
- getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ final ListView listView = getListView();
+ listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ listView.setTextDirection(textDirection);
+ listView.setTextAlignment(textAlignment);
setSelection(Spinner.this.getSelectedItemPosition());
+ if (wasShowing) {
+ // Skip setting up the layout/dismiss listener below. If we were previously
+ // showing it will still stick around.
+ return;
+ }
+
// Make sure we hide if our anchor goes away.
// TODO: This might be appropriate to push all the way down to PopupWindow,
// but it may have other side effects to investigate first. (Text editing handles, etc.)
@@ -992,6 +1079,12 @@ public class Spinner extends AbsSpinner implements OnClickListener {
public void onGlobalLayout() {
if (!Spinner.this.isVisibleToUser()) {
dismiss();
+ } else {
+ computeContentWidth();
+
+ // Use super.show here to update; we don't want to move the selected
+ // position or adjust other things that would be reset otherwise.
+ DropdownPopup.super.show();
}
}
};
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index 399b4fa..f4b2ce0 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -445,7 +445,7 @@ public class TableLayout extends LinearLayout {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// enforce vertical layout
- layoutVertical();
+ layoutVertical(l, t, r, b);
}
/**
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index 35927e0..fe3631a 100644
--- a/core/java/android/widget/TableRow.java
+++ b/core/java/android/widget/TableRow.java
@@ -120,7 +120,7 @@ public class TableRow extends LinearLayout {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// enforce horizontal layout
- layoutHorizontal();
+ layoutHorizontal(l, t, r, b);
}
/**
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 2f08253..a564c96 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -397,6 +397,16 @@ public class TextClock extends TextView {
}
/**
+ * Returns the current format string. Always valid after constructor has
+ * finished, and will never be {@code null}.
+ *
+ * @hide
+ */
+ public CharSequence getFormat() {
+ return mFormat;
+ }
+
+ /**
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
* depending on whether the user has selected 24-hour format.
*
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7c1b959..04c4070 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -26,6 +26,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Canvas;
+import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
@@ -135,6 +136,8 @@ import java.util.ArrayList;
import java.util.Locale;
import java.util.concurrent.locks.ReentrantLock;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+
/**
* Displays text to the user and optionally allows them to edit it. A TextView
* is a complete text editor, however the basic class is configured to not
@@ -300,6 +303,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
+ Drawable mDrawableLeftInitial, mDrawableRightInitial;
+ boolean mIsRtlCompatibilityMode;
+ boolean mOverride;
+
int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
@@ -310,38 +317,64 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int mDrawableSaved = DRAWABLE_NONE;
- public void resolveWithLayoutDirection(int layoutDirection) {
- switch(layoutDirection) {
- case LAYOUT_DIRECTION_RTL:
- if (mDrawableStart != null) {
- mDrawableRight = mDrawableStart;
-
- mDrawableSizeRight = mDrawableSizeStart;
- mDrawableHeightRight = mDrawableHeightStart;
- }
- if (mDrawableEnd != null) {
- mDrawableLeft = mDrawableEnd;
+ public Drawables(Context context) {
+ final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
+ mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
+ !context.getApplicationInfo().hasRtlSupport());
+ mOverride = false;
+ }
- mDrawableSizeLeft = mDrawableSizeEnd;
- mDrawableHeightLeft = mDrawableHeightEnd;
- }
- break;
+ public void resolveWithLayoutDirection(int layoutDirection) {
+ // First reset "left" and "right" drawables to their initial values
+ mDrawableLeft = mDrawableLeftInitial;
+ mDrawableRight = mDrawableRightInitial;
+
+ if (mIsRtlCompatibilityMode) {
+ // Use "start" drawable as "left" drawable if the "left" drawable was not defined
+ if (mDrawableStart != null && mDrawableLeft == null) {
+ mDrawableLeft = mDrawableStart;
+ mDrawableSizeLeft = mDrawableSizeStart;
+ mDrawableHeightLeft = mDrawableHeightStart;
+ }
+ // Use "end" drawable as "right" drawable if the "right" drawable was not defined
+ if (mDrawableEnd != null && mDrawableRight == null) {
+ mDrawableRight = mDrawableEnd;
+ mDrawableSizeRight = mDrawableSizeEnd;
+ mDrawableHeightRight = mDrawableHeightEnd;
+ }
+ } else {
+ // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
+ // drawable if and only if they have been defined
+ switch(layoutDirection) {
+ case LAYOUT_DIRECTION_RTL:
+ if (mOverride) {
+ mDrawableRight = mDrawableStart;
+ mDrawableSizeRight = mDrawableSizeStart;
+ mDrawableHeightRight = mDrawableHeightStart;
+ }
- case LAYOUT_DIRECTION_LTR:
- default:
- if (mDrawableStart != null) {
- mDrawableLeft = mDrawableStart;
+ if (mOverride) {
+ mDrawableLeft = mDrawableEnd;
+ mDrawableSizeLeft = mDrawableSizeEnd;
+ mDrawableHeightLeft = mDrawableHeightEnd;
+ }
+ break;
- mDrawableSizeLeft = mDrawableSizeStart;
- mDrawableHeightLeft = mDrawableHeightStart;
- }
- if (mDrawableEnd != null) {
- mDrawableRight = mDrawableEnd;
+ case LAYOUT_DIRECTION_LTR:
+ default:
+ if (mOverride) {
+ mDrawableLeft = mDrawableStart;
+ mDrawableSizeLeft = mDrawableSizeStart;
+ mDrawableHeightLeft = mDrawableHeightStart;
+ }
- mDrawableSizeRight = mDrawableSizeEnd;
- mDrawableHeightRight = mDrawableHeightEnd;
- }
- break;
+ if (mOverride) {
+ mDrawableRight = mDrawableEnd;
+ mDrawableSizeRight = mDrawableSizeEnd;
+ mDrawableHeightRight = mDrawableHeightEnd;
+ }
+ break;
+ }
}
applyErrorDrawableIfNeeded(layoutDirection);
updateDrawablesLayoutDirection(layoutDirection);
@@ -438,10 +471,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mMarqueeRepeatLimit = 3;
- // The alignment to pass to Layout, or null if not resolved.
- private Layout.Alignment mLayoutAlignment;
- private int mResolvedTextAlignment;
-
private int mLastLayoutDirection = -1;
/**
@@ -517,7 +546,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private InputFilter[] mFilters = NO_FILTERS;
- private volatile Locale mCurrentTextServicesLocaleCache;
+ private volatile Locale mCurrentSpellCheckerLocaleCache;
private final ReentrantLock mCurrentTextServicesLocaleLock = new ReentrantLock();
// It is possible to have a selection even when mEditor is null (programmatically set, like when
@@ -613,6 +642,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int typefaceIndex = -1;
int styleIndex = -1;
boolean allCaps = false;
+ int shadowcolor = 0;
+ float dx = 0, dy = 0, r = 0;
final Resources.Theme theme = context.getTheme();
@@ -673,6 +704,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.TextAppearance_textAllCaps:
allCaps = appearance.getBoolean(attr, false);
break;
+
+ case com.android.internal.R.styleable.TextAppearance_shadowColor:
+ shadowcolor = a.getInt(attr, 0);
+ break;
+
+ case com.android.internal.R.styleable.TextAppearance_shadowDx:
+ dx = a.getFloat(attr, 0);
+ break;
+
+ case com.android.internal.R.styleable.TextAppearance_shadowDy:
+ dy = a.getFloat(attr, 0);
+ break;
+
+ case com.android.internal.R.styleable.TextAppearance_shadowRadius:
+ r = a.getFloat(attr, 0);
+ break;
}
}
@@ -696,8 +743,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int maxlength = -1;
CharSequence text = "";
CharSequence hint = null;
- int shadowcolor = 0;
- float dx = 0, dy = 0, r = 0;
boolean password = false;
int inputType = EditorInfo.TYPE_NULL;
@@ -1148,6 +1193,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
bufferType = BufferType.SPANNABLE;
}
+ // This call will save the initial left/right drawables
setCompoundDrawablesWithIntrinsicBounds(
drawableLeft, drawableTop, drawableRight, drawableBottom);
setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
@@ -1296,8 +1342,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (hasRelativeDrawables) {
Drawables dr = mDrawables;
if (dr == null) {
- mDrawables = dr = new Drawables();
+ mDrawables = dr = new Drawables(getContext());
}
+ mDrawables.mOverride = true;
final Rect compoundRect = dr.mCompoundRect;
int[] state = getDrawableState();
if (start != null) {
@@ -1870,9 +1917,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
} else {
if (dr == null) {
- mDrawables = dr = new Drawables();
+ mDrawables = dr = new Drawables(getContext());
}
+ mDrawables.mOverride = false;
+
if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
dr.mDrawableLeft.setCallback(null);
}
@@ -1939,6 +1988,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ // Save initial left/right drawables
+ if (dr != null) {
+ dr.mDrawableLeftInitial = left;
+ dr.mDrawableRightInitial = right;
+ }
+
invalidate();
requestLayout();
}
@@ -2039,9 +2094,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
} else {
if (dr == null) {
- mDrawables = dr = new Drawables();
+ mDrawables = dr = new Drawables(getContext());
}
+ mDrawables.mOverride = true;
+
if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
dr.mDrawableStart.setCallback(null);
}
@@ -2108,6 +2165,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ resetResolvedDrawables();
resolveDrawables();
invalidate();
requestLayout();
@@ -2132,7 +2190,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@android.view.RemotableViewMethod
public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
int bottom) {
- resetResolvedDrawables();
final Resources resources = getContext().getResources();
setCompoundDrawablesRelativeWithIntrinsicBounds(
start != 0 ? resources.getDrawable(start) : null,
@@ -2155,7 +2212,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable start, Drawable top,
Drawable end, Drawable bottom) {
- resetResolvedDrawables();
if (start != null) {
start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
}
@@ -2224,7 +2280,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
} else {
if (dr == null) {
- mDrawables = dr = new Drawables();
+ mDrawables = dr = new Drawables(getContext());
}
dr.mDrawablePadding = pad;
}
@@ -2337,6 +2393,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex);
+ final int shadowcolor = appearance.getInt(
+ com.android.internal.R.styleable.TextAppearance_shadowColor, 0);
+ if (shadowcolor != 0) {
+ final float dx = appearance.getFloat(
+ com.android.internal.R.styleable.TextAppearance_shadowDx, 0);
+ final float dy = appearance.getFloat(
+ com.android.internal.R.styleable.TextAppearance_shadowDy, 0);
+ final float r = appearance.getFloat(
+ com.android.internal.R.styleable.TextAppearance_shadowRadius, 0);
+
+ setShadowLayer(r, dx, dy, shadowcolor);
+ }
+
if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps,
false)) {
setTransformationMethod(new AllCapsTransformationMethod(getContext()));
@@ -2836,7 +2905,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (gravity != mGravity) {
invalidate();
- mLayoutAlignment = null;
}
mGravity = gravity;
@@ -4356,6 +4424,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/////////////////////////////////////////////////////////////////////////
+ private int getBoxHeight(Layout l) {
+ Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
+ int padding = (l == mHintLayout) ?
+ getCompoundPaddingTop() + getCompoundPaddingBottom() :
+ getExtendedPaddingTop() + getExtendedPaddingBottom();
+ return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
+ }
+
int getVerticalOffset(boolean forceNormal) {
int voffset = 0;
final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
@@ -4366,15 +4442,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (gravity != Gravity.TOP) {
- int boxht;
-
- if (l == mHintLayout) {
- boxht = getMeasuredHeight() - getCompoundPaddingTop() -
- getCompoundPaddingBottom();
- } else {
- boxht = getMeasuredHeight() - getExtendedPaddingTop() -
- getExtendedPaddingBottom();
- }
+ int boxht = getBoxHeight(l);
int textht = l.getHeight();
if (textht < boxht) {
@@ -4397,15 +4465,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (gravity != Gravity.BOTTOM) {
- int boxht;
-
- if (l == mHintLayout) {
- boxht = getMeasuredHeight() - getCompoundPaddingTop() -
- getCompoundPaddingBottom();
- } else {
- boxht = getMeasuredHeight() - getExtendedPaddingTop() -
- getExtendedPaddingBottom();
- }
+ int boxht = getBoxHeight(l);
int textht = l.getHeight();
if (textht < boxht) {
@@ -4741,7 +4801,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public boolean hasOverlappingRendering() {
- return (getBackground() != null || mText instanceof Spannable || hasSelection());
+ return ((getBackground() != null && getBackground().getCurrent() != null)
+ || mText instanceof Spannable || hasSelection());
}
/**
@@ -5158,6 +5219,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
voffset = getVerticalOffset(true);
}
+ if (isLayoutModeOptical(mParent)) {
+ voffset -= getOpticalInsets().top;
+ }
+
return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
}
@@ -5793,68 +5858,56 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
physicalWidth, false);
}
- @Override
- public void onRtlPropertiesChanged(int layoutDirection) {
- if (mLayoutAlignment != null) {
- if (mResolvedTextAlignment == TEXT_ALIGNMENT_VIEW_START ||
- mResolvedTextAlignment == TEXT_ALIGNMENT_VIEW_END) {
- mLayoutAlignment = null;
- }
- }
- }
-
private Layout.Alignment getLayoutAlignment() {
- if (mLayoutAlignment == null) {
- mResolvedTextAlignment = getTextAlignment();
- switch (mResolvedTextAlignment) {
- case TEXT_ALIGNMENT_GRAVITY:
- switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
- case Gravity.START:
- mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
- break;
- case Gravity.END:
- mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE;
- break;
- case Gravity.LEFT:
- mLayoutAlignment = Layout.Alignment.ALIGN_LEFT;
- break;
- case Gravity.RIGHT:
- mLayoutAlignment = Layout.Alignment.ALIGN_RIGHT;
- break;
- case Gravity.CENTER_HORIZONTAL:
- mLayoutAlignment = Layout.Alignment.ALIGN_CENTER;
- break;
- default:
- mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
- break;
- }
- break;
- case TEXT_ALIGNMENT_TEXT_START:
- mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
- break;
- case TEXT_ALIGNMENT_TEXT_END:
- mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE;
- break;
- case TEXT_ALIGNMENT_CENTER:
- mLayoutAlignment = Layout.Alignment.ALIGN_CENTER;
- break;
- case TEXT_ALIGNMENT_VIEW_START:
- mLayoutAlignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
- Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
- break;
- case TEXT_ALIGNMENT_VIEW_END:
- mLayoutAlignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
- Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
- break;
- case TEXT_ALIGNMENT_INHERIT:
- // This should never happen as we have already resolved the text alignment
- // but better safe than sorry so we just fall through
- default:
- mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL;
- break;
- }
+ Layout.Alignment alignment;
+ switch (getTextAlignment()) {
+ case TEXT_ALIGNMENT_GRAVITY:
+ switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.START:
+ alignment = Layout.Alignment.ALIGN_NORMAL;
+ break;
+ case Gravity.END:
+ alignment = Layout.Alignment.ALIGN_OPPOSITE;
+ break;
+ case Gravity.LEFT:
+ alignment = Layout.Alignment.ALIGN_LEFT;
+ break;
+ case Gravity.RIGHT:
+ alignment = Layout.Alignment.ALIGN_RIGHT;
+ break;
+ case Gravity.CENTER_HORIZONTAL:
+ alignment = Layout.Alignment.ALIGN_CENTER;
+ break;
+ default:
+ alignment = Layout.Alignment.ALIGN_NORMAL;
+ break;
+ }
+ break;
+ case TEXT_ALIGNMENT_TEXT_START:
+ alignment = Layout.Alignment.ALIGN_NORMAL;
+ break;
+ case TEXT_ALIGNMENT_TEXT_END:
+ alignment = Layout.Alignment.ALIGN_OPPOSITE;
+ break;
+ case TEXT_ALIGNMENT_CENTER:
+ alignment = Layout.Alignment.ALIGN_CENTER;
+ break;
+ case TEXT_ALIGNMENT_VIEW_START:
+ alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+ Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
+ break;
+ case TEXT_ALIGNMENT_VIEW_END:
+ alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
+ Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
+ break;
+ case TEXT_ALIGNMENT_INHERIT:
+ // This should never happen as we have already resolved the text alignment
+ // but better safe than sorry so we just fall through
+ default:
+ alignment = Layout.Alignment.ALIGN_NORMAL;
+ break;
}
- return mLayoutAlignment;
+ return alignment;
}
/**
@@ -5882,6 +5935,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
Layout.Alignment alignment = getLayoutAlignment();
+ final boolean testDirChange = mSingleLine && mLayout != null &&
+ (alignment == Layout.Alignment.ALIGN_NORMAL ||
+ alignment == Layout.Alignment.ALIGN_OPPOSITE);
+ int oldDir = 0;
+ if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
@@ -5970,7 +6028,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- if (bringIntoView) {
+ if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
registerForPreDraw();
}
@@ -6154,7 +6212,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
if (mTextDir == null) {
- getTextDirectionHeuristic();
+ mTextDir = getTextDirectionHeuristic();
}
int des = -1;
@@ -6476,7 +6534,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mDeferScroll = -1;
bringPointIntoView(Math.min(curs, mText.length()));
}
- if (changed && mEditor != null) mEditor.invalidateTextDisplayList();
}
private boolean isShowingHint() {
@@ -6570,15 +6627,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int line = layout.getLineForOffset(offset);
- // FIXME: Is it okay to truncate this, or should we round?
- final int x = (int)layout.getPrimaryHorizontal(offset);
- final int top = layout.getLineTop(line);
- final int bottom = layout.getLineTop(line + 1);
-
- int left = (int) FloatMath.floor(layout.getLineLeft(line));
- int right = (int) FloatMath.ceil(layout.getLineRight(line));
- int ht = layout.getHeight();
-
int grav;
switch (layout.getParagraphAlignment(line)) {
@@ -6600,8 +6648,32 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
}
+ // We only want to clamp the cursor to fit within the layout width
+ // in left-to-right modes, because in a right to left alignment,
+ // we want to scroll to keep the line-right on the screen, as other
+ // lines are likely to have text flush with the right margin, which
+ // we want to keep visible.
+ // A better long-term solution would probably be to measure both
+ // the full line and a blank-trimmed version, and, for example, use
+ // the latter measurement for centering and right alignment, but for
+ // the time being we only implement the cursor clamping in left to
+ // right where it is most likely to be annoying.
+ final boolean clamped = grav > 0;
+ // FIXME: Is it okay to truncate this, or should we round?
+ final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
+ final int top = layout.getLineTop(line);
+ final int bottom = layout.getLineTop(line + 1);
+
+ int left = (int) FloatMath.floor(layout.getLineLeft(line));
+ int right = (int) FloatMath.ceil(layout.getLineRight(line));
+ int ht = layout.getHeight();
+
int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
+ if (!mHorizontallyScrolling && right - left > hspace && right > x) {
+ // If cursor has been clamped, make sure we don't scroll.
+ right = Math.max(x, left + hspace);
+ }
int hslack = (bottom - top) / 2;
int vslack = hslack;
@@ -7168,6 +7240,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
protected void onSelectionChanged(int selStart, int selEnd) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
+ notifyAccessibilityStateChanged();
}
/**
@@ -7744,7 +7817,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Returns the TextView_textColor attribute from the
- * Resources.StyledAttributes, if set, or the TextAppearance_textColor
+ * TypedArray, if set, or the TextAppearance_textColor
* from the TextView_textAppearance attribute, if TextView_textColor
* was not set directly.
*/
@@ -7842,27 +7915,46 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
(isTextSelectable() && mText instanceof Spannable && isEnabled());
}
+ private Locale getTextServicesLocale(boolean allowNullLocale) {
+ // Start fetching the text services locale asynchronously.
+ updateTextServicesLocaleAsync();
+ // If !allowNullLocale and there is no cached text services locale, just return the default
+ // locale.
+ return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
+ : mCurrentSpellCheckerLocaleCache;
+ }
+
/**
* This is a temporary method. Future versions may support multi-locale text.
* Caveat: This method may not return the latest text services locale, but this should be
* acceptable and it's more important to make this method asynchronous.
*
- * @return The locale that should be used for a word iterator and a spell checker
+ * @return The locale that should be used for a word iterator
* in this TextView, based on the current spell checker settings,
* the current IME's locale, or the system default locale.
+ * Please note that a word iterator in this TextView is different from another word iterator
+ * used by SpellChecker.java of TextView. This method should be used for the former.
* @hide
*/
// TODO: Support multi-locale
// TODO: Update the text services locale immediately after the keyboard locale is switched
// by catching intent of keyboard switch event
public Locale getTextServicesLocale() {
- if (mCurrentTextServicesLocaleCache == null) {
- // If there is no cached text services locale, just return the default locale.
- mCurrentTextServicesLocaleCache = Locale.getDefault();
- }
- // Start fetching the text services locale asynchronously.
- updateTextServicesLocaleAsync();
- return mCurrentTextServicesLocaleCache;
+ return getTextServicesLocale(false /* allowNullLocale */);
+ }
+
+ /**
+ * This is a temporary method. Future versions may support multi-locale text.
+ * Caveat: This method may not return the latest spell checker locale, but this should be
+ * acceptable and it's more important to make this method asynchronous.
+ *
+ * @return The locale that should be used for a spell checker in this TextView,
+ * based on the current spell checker settings, the current IME's locale, or the system default
+ * locale.
+ * @hide
+ */
+ public Locale getSpellCheckerLocale() {
+ return getTextServicesLocale(true /* allowNullLocale */);
}
private void updateTextServicesLocaleAsync() {
@@ -7881,14 +7973,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void updateTextServicesLocaleLocked() {
- Locale locale = Locale.getDefault();
final TextServicesManager textServicesManager = (TextServicesManager)
mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
+ final Locale locale;
if (subtype != null) {
locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
+ } else {
+ locale = null;
}
- mCurrentTextServicesLocaleCache = locale;
+ mCurrentSpellCheckerLocaleCache = locale;
}
void onLocaleChanged() {
@@ -7958,7 +8052,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
info.setText(getTextForAccessibility());
}
- if (TextUtils.isEmpty(getContentDescription()) && !TextUtils.isEmpty(mText)) {
+ if (mBufferType == BufferType.EDITABLE) {
+ info.setEditable(true);
+ }
+
+ if (!TextUtils.isEmpty(mText)) {
info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
@@ -7967,6 +8065,83 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
| AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
| AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
}
+
+ if (isFocused()) {
+ if (canSelectText()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
+ }
+ if (canCopy()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_COPY);
+ }
+ if (canPaste()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
+ }
+ if (canCut()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_CUT);
+ }
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_COPY: {
+ if (isFocused() && canCopy()) {
+ if (onTextContextMenuItem(ID_COPY)) {
+ notifyAccessibilityStateChanged();
+ return true;
+ }
+ }
+ } return false;
+ case AccessibilityNodeInfo.ACTION_PASTE: {
+ if (isFocused() && canPaste()) {
+ if (onTextContextMenuItem(ID_PASTE)) {
+ notifyAccessibilityStateChanged();
+ return true;
+ }
+ }
+ } return false;
+ case AccessibilityNodeInfo.ACTION_CUT: {
+ if (isFocused() && canCut()) {
+ if (onTextContextMenuItem(ID_CUT)) {
+ notifyAccessibilityStateChanged();
+ return true;
+ }
+ }
+ } return false;
+ case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
+ if (isFocused() && canSelectText()) {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text == null) {
+ return false;
+ }
+ final int start = (arguments != null) ? arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
+ final int end = (arguments != null) ? arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
+ if ((getSelectionStart() != start || getSelectionEnd() != end)) {
+ // No arguments clears the selection.
+ if (start == end && end == -1) {
+ Selection.removeSelection((Spannable) text);
+ notifyAccessibilityStateChanged();
+ return true;
+ }
+ if (start >= 0 && start <= end && end <= text.length()) {
+ Selection.setSelection((Spannable) text, start, end);
+ // Make sure selection mode is engaged.
+ if (mEditor != null) {
+ mEditor.startSelectionActionMode();
+ }
+ notifyAccessibilityStateChanged();
+ return true;
+ }
+ }
+ }
+ } return false;
+ default: {
+ return super.performAccessibilityAction(action, arguments);
+ }
+ }
}
@Override
@@ -8380,6 +8555,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return mEditor.mInBatchEditControllers;
}
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+
+ mTextDir = getTextDirectionHeuristic();
+ }
+
TextDirectionHeuristic getTextDirectionHeuristic() {
if (hasPasswordTransformationMethod()) {
// passwords fields should be LTR
@@ -8495,13 +8677,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@Override
public CharSequence getIterableTextForAccessibility() {
- if (!TextUtils.isEmpty(mText)) {
- if (!(mText instanceof Spannable)) {
- setText(mText, BufferType.SPANNABLE);
- }
- return mText;
+ if (!(mText instanceof Spannable)) {
+ setText(mText, BufferType.SPANNABLE);
}
- return super.getIterableTextForAccessibility();
+ return mText;
}
/**
@@ -8536,32 +8715,45 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @hide
*/
@Override
- public int getAccessibilityCursorPosition() {
- if (TextUtils.isEmpty(getContentDescription())) {
- final int selectionEnd = getSelectionEnd();
- if (selectionEnd >= 0) {
- return selectionEnd;
- }
- }
- return super.getAccessibilityCursorPosition();
+ public int getAccessibilitySelectionStart() {
+ return getSelectionStart();
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isAccessibilitySelectionExtendable() {
+ return true;
}
/**
* @hide
*/
@Override
- public void setAccessibilityCursorPosition(int index) {
- if (getAccessibilityCursorPosition() == index) {
+ public int getAccessibilitySelectionEnd() {
+ return getSelectionEnd();
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void setAccessibilitySelection(int start, int end) {
+ if (getAccessibilitySelectionStart() == start
+ && getAccessibilitySelectionEnd() == end) {
return;
}
- if (TextUtils.isEmpty(getContentDescription())) {
- if (index >= 0 && index <= mText.length()) {
- Selection.setSelection((Spannable) mText, index);
- } else {
- Selection.removeSelection((Spannable) mText);
- }
+ // Hide all selection controllers used for adjusting selection
+ // since we are doing so explicitlty by other means and these
+ // controllers interact with how selection behaves.
+ if (mEditor != null) {
+ mEditor.hideControllers();
+ }
+ CharSequence text = getIterableTextForAccessibility();
+ if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
+ Selection.setSelection((Spannable) text, start, end);
} else {
- super.setAccessibilityCursorPosition(index);
+ Selection.removeSelection((Spannable) text);
}
}
@@ -8708,16 +8900,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
advancesIndex);
}
- public float getTextRunAdvances(int start, int end, int contextStart,
- int contextEnd, int flags, float[] advances, int advancesIndex,
- Paint p, int reserved) {
- int count = end - start;
- int contextCount = contextEnd - contextStart;
- return p.getTextRunAdvances(mChars, start + mStart, count,
- contextStart + mStart, contextCount, flags, advances,
- advancesIndex, reserved);
- }
-
public int getTextRunCursor(int contextStart, int contextEnd, int flags,
int offset, int cursorOpt, Paint p) {
int contextCount = contextEnd - contextStart;
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 329b0df..ebf9fe0 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -75,6 +75,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
// All the stuff we need for playing and showing a video
private SurfaceHolder mSurfaceHolder = null;
private MediaPlayer mMediaPlayer = null;
+ private int mAudioSession;
private int mVideoWidth;
private int mVideoHeight;
private int mSurfaceWidth;
@@ -107,23 +108,65 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- //Log.i("@@@@", "onMeasure");
+ //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ // + MeasureSpec.toString(heightMeasureSpec) + ")");
+
int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
if (mVideoWidth > 0 && mVideoHeight > 0) {
- if ( mVideoWidth * height > width * mVideoHeight ) {
- //Log.i("@@@", "image too tall, correcting");
+
+ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
+ // the size is fixed
+ width = widthSpecSize;
+ height = heightSpecSize;
+
+ // for compatibility, we adjust size based on aspect ratio
+ if ( mVideoWidth * height < width * mVideoHeight ) {
+ //Log.i("@@@", "image too wide, correcting");
+ width = height * mVideoWidth / mVideoHeight;
+ } else if ( mVideoWidth * height > width * mVideoHeight ) {
+ //Log.i("@@@", "image too tall, correcting");
+ height = width * mVideoHeight / mVideoWidth;
+ }
+ } else if (widthSpecMode == MeasureSpec.EXACTLY) {
+ // only the width is fixed, adjust the height to match aspect ratio if possible
+ width = widthSpecSize;
height = width * mVideoHeight / mVideoWidth;
- } else if ( mVideoWidth * height < width * mVideoHeight ) {
- //Log.i("@@@", "image too wide, correcting");
+ if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+ // couldn't match aspect ratio within the constraints
+ height = heightSpecSize;
+ }
+ } else if (heightSpecMode == MeasureSpec.EXACTLY) {
+ // only the height is fixed, adjust the width to match aspect ratio if possible
+ height = heightSpecSize;
width = height * mVideoWidth / mVideoHeight;
+ if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+ // couldn't match aspect ratio within the constraints
+ width = widthSpecSize;
+ }
} else {
- //Log.i("@@@", "aspect ratio is correct: " +
- //width+"/"+height+"="+
- //mVideoWidth+"/"+mVideoHeight);
+ // neither the width nor the height are fixed, try to use actual video size
+ width = mVideoWidth;
+ height = mVideoHeight;
+ if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+ // too tall, decrease both width and height
+ height = heightSpecSize;
+ width = height * mVideoWidth / mVideoHeight;
+ }
+ if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+ // too wide, decrease both width and height
+ width = widthSpecSize;
+ height = width * mVideoHeight / mVideoWidth;
+ }
}
+ } else {
+ // no size yet, just adopt the given spec sizes
}
- //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
setMeasuredDimension(width, height);
}
@@ -140,33 +183,8 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
public int resolveAdjustedSize(int desiredSize, int measureSpec) {
- int result = desiredSize;
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
-
- switch (specMode) {
- case MeasureSpec.UNSPECIFIED:
- /* Parent says we can be as big as we want. Just don't be larger
- * than max size imposed on ourselves.
- */
- result = desiredSize;
- break;
-
- case MeasureSpec.AT_MOST:
- /* Parent says we can be as big as we want, up to specSize.
- * Don't be larger than specSize, and don't be larger than
- * the max size imposed on ourselves.
- */
- result = Math.min(desiredSize, specSize);
- break;
-
- case MeasureSpec.EXACTLY:
- // No choice. Do what we are told.
- result = specSize;
- break;
- }
- return result;
-}
+ return getDefaultSize(desiredSize, measureSpec);
+ }
private void initVideoView() {
mVideoWidth = 0;
@@ -226,6 +244,11 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
release(false);
try {
mMediaPlayer = new MediaPlayer();
+ if (mAudioSession != 0) {
+ mMediaPlayer.setAudioSessionId(mAudioSession);
+ } else {
+ mAudioSession = mMediaPlayer.getAudioSessionId();
+ }
mMediaPlayer.setOnPreparedListener(mPreparedListener);
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
mMediaPlayer.setOnCompletionListener(mCompletionListener);
@@ -580,6 +603,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
}
+ @Override
public void start() {
if (isInPlaybackState()) {
mMediaPlayer.start();
@@ -588,6 +612,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mTargetState = STATE_PLAYING;
}
+ @Override
public void pause() {
if (isInPlaybackState()) {
if (mMediaPlayer.isPlaying()) {
@@ -606,6 +631,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
openVideo();
}
+ @Override
public int getDuration() {
if (isInPlaybackState()) {
return mMediaPlayer.getDuration();
@@ -614,6 +640,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
return -1;
}
+ @Override
public int getCurrentPosition() {
if (isInPlaybackState()) {
return mMediaPlayer.getCurrentPosition();
@@ -621,6 +648,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
return 0;
}
+ @Override
public void seekTo(int msec) {
if (isInPlaybackState()) {
mMediaPlayer.seekTo(msec);
@@ -630,10 +658,12 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
}
+ @Override
public boolean isPlaying() {
return isInPlaybackState() && mMediaPlayer.isPlaying();
}
+ @Override
public int getBufferPercentage() {
if (mMediaPlayer != null) {
return mCurrentBufferPercentage;
@@ -648,15 +678,28 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mCurrentState != STATE_PREPARING);
}
+ @Override
public boolean canPause() {
return mCanPause;
}
+ @Override
public boolean canSeekBackward() {
return mCanSeekBack;
}
+ @Override
public boolean canSeekForward() {
return mCanSeekForward;
}
+
+ @Override
+ public int getAudioSessionId() {
+ if (mAudioSession == 0) {
+ MediaPlayer foo = new MediaPlayer();
+ mAudioSession = foo.getAudioSessionId();
+ foo.release();
+ }
+ return mAudioSession;
+ }
}
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 6233522..d4b4e5e 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -110,6 +110,7 @@ public class ActionBarImpl extends ActionBar {
private int mCurWindowVisibility = View.VISIBLE;
+ private boolean mContentAnimations = true;
private boolean mHiddenByApp;
private boolean mHiddenBySystem;
private boolean mShowingForMode;
@@ -122,7 +123,7 @@ public class ActionBarImpl extends ActionBar {
final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- if (mContentView != null) {
+ if (mContentAnimations && mContentView != null) {
mContentView.setTranslationY(0);
mTopVisibilityView.setTranslationY(0);
}
@@ -151,15 +152,16 @@ public class ActionBarImpl extends ActionBar {
mActivity = activity;
Window window = activity.getWindow();
View decor = window.getDecorView();
- init(decor);
- if (!mActivity.getWindow().hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY)) {
+ boolean overlayMode = mActivity.getWindow().hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+ init(decor, overlayMode);
+ if (!overlayMode) {
mContentView = decor.findViewById(android.R.id.content);
}
}
public ActionBarImpl(Dialog dialog) {
mDialog = dialog;
- init(dialog.getWindow().getDecorView());
+ init(dialog.getWindow().getDecorView(), false);
}
/**
@@ -168,15 +170,15 @@ public class ActionBarImpl extends ActionBar {
*/
public ActionBarImpl(View layout) {
assert layout.isInEditMode();
- init(layout);
+ init(layout, false);
}
- private void init(View decor) {
+ private void init(View decor, boolean overlayMode) {
mContext = decor.getContext();
mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(
com.android.internal.R.id.action_bar_overlay_layout);
if (mOverlayLayout != null) {
- mOverlayLayout.setActionBar(this);
+ mOverlayLayout.setActionBar(this, overlayMode);
}
mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
mContextView = (ActionBarContextView) decor.findViewById(
@@ -595,6 +597,10 @@ public class ActionBarImpl extends ActionBar {
return mContainerView.getHeight();
}
+ public void enableContentAnimations(boolean enabled) {
+ mContentAnimations = enabled;
+ }
+
@Override
public void show() {
if (mHiddenByApp) {
@@ -693,7 +699,7 @@ public class ActionBarImpl extends ActionBar {
AnimatorSet anim = new AnimatorSet();
AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mTopVisibilityView,
"translationY", 0));
- if (mContentView != null) {
+ if (mContentAnimations && mContentView != null) {
b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
startingY, 0));
}
@@ -718,7 +724,7 @@ public class ActionBarImpl extends ActionBar {
} else {
mTopVisibilityView.setAlpha(1);
mTopVisibilityView.setTranslationY(0);
- if (mContentView != null) {
+ if (mContentAnimations && mContentView != null) {
mContentView.setTranslationY(0);
}
if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) {
@@ -751,7 +757,7 @@ public class ActionBarImpl extends ActionBar {
}
AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mTopVisibilityView,
"translationY", endingY));
- if (mContentView != null) {
+ if (mContentAnimations && mContentView != null) {
b.with(ObjectAnimator.ofFloat(mContentView, "translationY",
0, endingY));
}
@@ -815,6 +821,26 @@ public class ActionBarImpl extends ActionBar {
return mActionView != null && mActionView.isTitleTruncated();
}
+ @Override
+ public void setHomeAsUpIndicator(Drawable indicator) {
+ mActionView.setHomeAsUpIndicator(indicator);
+ }
+
+ @Override
+ public void setHomeAsUpIndicator(int resId) {
+ mActionView.setHomeAsUpIndicator(resId);
+ }
+
+ @Override
+ public void setHomeActionContentDescription(CharSequence description) {
+ mActionView.setHomeActionContentDescription(description);
+ }
+
+ @Override
+ public void setHomeActionContentDescription(int resId) {
+ mActionView.setHomeActionContentDescription(resId);
+ }
+
/**
* @hide
*/
diff --git a/core/java/com/android/internal/app/IAppOpsCallback.aidl b/core/java/com/android/internal/app/IAppOpsCallback.aidl
new file mode 100644
index 0000000..afbc609
--- /dev/null
+++ b/core/java/com/android/internal/app/IAppOpsCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+// This interface is also used by native code, so must
+// be kept in sync with frameworks/native/include/binder/IAppOpsCallback.h
+oneway interface IAppOpsCallback {
+ void opChanged(int op, String packageName);
+}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
new file mode 100644
index 0000000..cfd9cc7
--- /dev/null
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.AppOpsManager;
+import com.android.internal.app.IAppOpsCallback;
+
+interface IAppOpsService {
+ // These first methods are also called by native code, so must
+ // be kept in sync with frameworks/native/include/binder/IAppOpsService.h
+ int checkOperation(int code, int uid, String packageName);
+ int noteOperation(int code, int uid, String packageName);
+ int startOperation(int code, int uid, String packageName);
+ void finishOperation(int code, int uid, String packageName);
+ void startWatchingMode(int op, String packageName, IAppOpsCallback callback);
+ void stopWatchingMode(IAppOpsCallback callback);
+
+ // Remaining methods are only used in Java.
+ List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
+ List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
+ void setMode(int code, int uid, String packageName, int mode);
+ void resetAllModes();
+}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 1a76461f..823e19f 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -37,6 +37,8 @@ interface IBatteryStats {
void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, int type);
void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, int type);
+ void noteVibratorOn(int uid, long durationMillis);
+ void noteVibratorOff(int uid);
void noteStartGps(int uid);
void noteStopGps(int uid);
void noteScreenOn();
diff --git a/core/java/com/android/internal/app/LocalePicker.java b/core/java/com/android/internal/app/LocalePicker.java
index f173327..043964f 100644
--- a/core/java/com/android/internal/app/LocalePicker.java
+++ b/core/java/com/android/internal/app/LocalePicker.java
@@ -38,6 +38,7 @@ import android.widget.TextView;
import java.text.Collator;
import java.util.Arrays;
import java.util.Locale;
+import java.util.ArrayList;
public class LocalePicker extends ListFragment {
private static final String TAG = "LocalePicker";
@@ -48,6 +49,10 @@ public class LocalePicker extends ListFragment {
public void onLocaleSelected(Locale locale);
}
+ protected boolean isInDeveloperMode() {
+ return false;
+ }
+
LocaleSelectionListener mListener; // default to null
public static class LocaleInfo implements Comparable<LocaleInfo> {
@@ -85,13 +90,39 @@ public class LocalePicker extends ListFragment {
* {@link LocaleInfo#label}.
*/
public static ArrayAdapter<LocaleInfo> constructAdapter(Context context) {
- return constructAdapter(context, R.layout.locale_picker_item, R.id.locale);
+ return constructAdapter(context, false /* disable pesudolocales */);
+ }
+
+ public static ArrayAdapter<LocaleInfo> constructAdapter(Context context,
+ final boolean isInDeveloperMode) {
+ return constructAdapter(context, R.layout.locale_picker_item, R.id.locale,
+ isInDeveloperMode);
}
public static ArrayAdapter<LocaleInfo> constructAdapter(Context context,
final int layoutId, final int fieldId) {
+ return constructAdapter(context, layoutId, fieldId, false /* disable pseudolocales */);
+ }
+
+ public static ArrayAdapter<LocaleInfo> constructAdapter(Context context,
+ final int layoutId, final int fieldId, final boolean isInDeveloperMode) {
final Resources resources = context.getResources();
- final String[] locales = Resources.getSystem().getAssets().getLocales();
+
+ ArrayList<String> localeList = new ArrayList<String>(Arrays.asList(
+ Resources.getSystem().getAssets().getLocales()));
+ if (isInDeveloperMode) {
+ if (!localeList.contains("zz_ZZ")) {
+ localeList.add("zz_ZZ");
+ }
+ /** - TODO: Enable when zz_ZY Pseudolocale is complete
+ * if (!localeList.contains("zz_ZY")) {
+ * localeList.add("zz_ZY");
+ * }
+ */
+ }
+ String[] locales = new String[localeList.size()];
+ locales = localeList.toArray(locales);
+
final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes);
final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names);
Arrays.sort(locales);
@@ -118,7 +149,8 @@ public class LocalePicker extends ListFragment {
// insert ours with full name
// diff lang -> insert ours with lang-only name
if (preprocess[finalSize-1].locale.getLanguage().equals(
- language)) {
+ language) &&
+ !preprocess[finalSize-1].locale.getLanguage().equals("zz")) {
if (DEBUG) {
Log.v(TAG, "backing up and fixing "+
preprocess[finalSize-1].label+" to "+
@@ -139,7 +171,9 @@ public class LocalePicker extends ListFragment {
} else {
String displayName;
if (s.equals("zz_ZZ")) {
- displayName = "Pseudo...";
+ displayName = "[Developer] Accented English";
+ } else if (s.equals("zz_ZY")) {
+ displayName = "[Developer] Fake Bi-Directional";
} else {
displayName = toTitleCase(l.getDisplayLanguage(l));
}
@@ -206,7 +240,8 @@ public class LocalePicker extends ListFragment {
@Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- final ArrayAdapter<LocaleInfo> adapter = constructAdapter(getActivity());
+ final ArrayAdapter<LocaleInfo> adapter = constructAdapter(getActivity(),
+ isInDeveloperMode());
setListAdapter(adapter);
}
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index 2bc80ff..e300021 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
@@ -70,7 +70,6 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
};
MediaRouter mRouter;
- DisplayManager mDisplayService;
private int mRouteTypes;
private LayoutInflater mInflater;
@@ -98,7 +97,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);
+ mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
}
@Override
@@ -121,15 +120,6 @@ 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() {
@@ -192,7 +182,6 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
list.setOnItemClickListener(mAdapter);
mListView = list;
- mRouter.addCallback(mRouteTypes, mCallback);
mAdapter.scrollToSelectedItem();
@@ -204,14 +193,6 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
return new RouteChooserDialog(getActivity(), getTheme());
}
- @Override
- public void onResume() {
- super.onResume();
- if (mDisplayService != null) {
- mDisplayService.scanWifiDisplays();
- }
- }
-
private static class ViewHolder {
public TextView text1;
public TextView text2;
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
index 78b4466..6d51d38 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
@@ -22,9 +22,9 @@ import android.widget.RemoteViews;
/** {@hide} */
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);
+ void updateAppWidget(int appWidgetId, in RemoteViews views, int userId);
+ void providerChanged(int appWidgetId, in AppWidgetProviderInfo info, int userId);
+ void providersChanged(int userId);
+ void viewDataChanged(int appWidgetId, int viewId, int userId);
}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index e685e63..7ddd5d2 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -26,42 +26,39 @@ import android.widget.RemoteViews;
/** {@hide} */
interface IAppWidgetService {
-
+
//
// for AppWidgetHost
//
int[] startListening(IAppWidgetHost host, String packageName, int hostId,
- out List<RemoteViews> updatedViews);
- int[] startListeningAsUser(IAppWidgetHost host, String packageName, int hostId,
out List<RemoteViews> updatedViews, int userId);
- void stopListening(int hostId);
- void stopListeningAsUser(int hostId, int userId);
- int allocateAppWidgetId(String packageName, int hostId);
- void deleteAppWidgetId(int appWidgetId);
- void deleteHost(int hostId);
- void deleteAllHosts();
- RemoteViews getAppWidgetViews(int appWidgetId);
- int[] getAppWidgetIdsForHost(int hostId);
+ void stopListening(int hostId, int userId);
+ int allocateAppWidgetId(String packageName, int hostId, int userId);
+ void deleteAppWidgetId(int appWidgetId, int userId);
+ void deleteHost(int hostId, int userId);
+ void deleteAllHosts(int userId);
+ RemoteViews getAppWidgetViews(int appWidgetId, int userId);
+ int[] getAppWidgetIdsForHost(int hostId, int userId);
//
// for AppWidgetManager
//
- void updateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views);
- void updateAppWidgetOptions(int appWidgetId, in Bundle extras);
- Bundle getAppWidgetOptions(int appWidgetId);
- void partiallyUpdateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views);
- void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views);
- void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, int viewId);
- List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter);
- AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId);
- boolean hasBindAppWidgetPermission(in String packageName);
- void setBindAppWidgetPermission(in String packageName, in boolean permission);
- void bindAppWidgetId(int appWidgetId, in ComponentName provider, in Bundle options);
- boolean bindAppWidgetIdIfAllowed(
- in String packageName, int appWidgetId, in ComponentName provider, in Bundle options);
+ void updateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views, int userId);
+ void updateAppWidgetOptions(int appWidgetId, in Bundle extras, int userId);
+ Bundle getAppWidgetOptions(int appWidgetId, int userId);
+ void partiallyUpdateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views, int userId);
+ void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views, int userId);
+ void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, int viewId, int userId);
+ List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter, int userId);
+ AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId, int userId);
+ boolean hasBindAppWidgetPermission(in String packageName, int userId);
+ void setBindAppWidgetPermission(in String packageName, in boolean permission, int userId);
+ void bindAppWidgetId(int appWidgetId, in ComponentName provider, in Bundle options, int userId);
+ boolean bindAppWidgetIdIfAllowed(in String packageName, int appWidgetId,
+ in ComponentName provider, in Bundle options, int userId);
void bindRemoteViewsService(int appWidgetId, in Intent intent, in IBinder connection, int userId);
void unbindRemoteViewsService(int appWidgetId, in Intent intent, int userId);
- int[] getAppWidgetIds(in ComponentName provider);
+ int[] getAppWidgetIds(in ComponentName provider, int userId);
}
diff --git a/core/java/com/android/internal/backup/IObbBackupService.aidl b/core/java/com/android/internal/backup/IObbBackupService.aidl
new file mode 100644
index 0000000..426dbc4
--- /dev/null
+++ b/core/java/com/android/internal/backup/IObbBackupService.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.backup;
+
+import android.app.backup.IBackupManager;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Interface for the Backup Manager Service to communicate with a helper service that
+ * handles local (whole-file) backup & restore of OBB content on behalf of applications.
+ * This can't be done within the Backup Manager Service itself because of the restrictions
+ * on system-user access to external storage, and can't be left to the apps because even
+ * apps that do not have permission to access external storage in the usual way can still
+ * use OBBs.
+ *
+ * {@hide}
+ */
+oneway interface IObbBackupService {
+ /*
+ * Back up a package's OBB directory tree
+ */
+ void backupObbs(in String packageName, in ParcelFileDescriptor data,
+ int token, in IBackupManager callbackBinder);
+
+ /*
+ * Restore an OBB file for the given package from the incoming stream
+ */
+ void restoreObbFile(in String pkgName, in ParcelFileDescriptor data,
+ long fileSize, int type, in String path, long mode, long mtime,
+ int token, in IBackupManager callbackBinder);
+}
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 20ecace..424c19b 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -153,8 +153,33 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
public void onPackageUpdateFinished(String packageName, int uid) {
}
-
- public void onPackageChanged(String packageName, int uid, String[] components) {
+
+ /**
+ * Direct reflection of {@link Intent#ACTION_PACKAGE_CHANGED
+ * Intent.ACTION_PACKAGE_CHANGED} being received, informing you of
+ * changes to the enabled/disabled state of components in a package
+ * and/or of the overall package.
+ *
+ * @param packageName The name of the package that is changing.
+ * @param uid The user ID the package runs under.
+ * @param components Any components in the package that are changing. If
+ * the overall package is changing, this will contain an entry of the
+ * package name itself.
+ * @return Return true to indicate you care about this change, which will
+ * result in {@link #onSomePackagesChanged()} being called later. If you
+ * return false, no further callbacks will happen about this change. The
+ * default implementation returns true if this is a change to the entire
+ * package.
+ */
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ if (components != null) {
+ for (String name : components) {
+ if (packageName.equals(name)) {
+ return true;
+ }
+ }
+ }
+ return false;
}
public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
@@ -189,7 +214,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
*/
public void onPackageAppeared(String packageName, int reason) {
}
-
+
+ /**
+ * Called when an existing package is updated or its disabled state changes.
+ */
public void onPackageModified(String packageName) {
}
@@ -328,9 +356,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
if (pkg != null) {
mModifiedPackages = mTempArray;
mTempArray[0] = pkg;
- onPackageChanged(pkg, uid, components);
- // XXX Don't want this to always cause mSomePackagesChanged,
- // since it can happen a fair amount.
+ mChangeType = PACKAGE_PERMANENT_CHANGE;
+ if (onPackageChanged(pkg, uid, components)) {
+ mSomePackagesChanged = true;
+ }
onPackageModified(pkg);
}
} else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
new file mode 100644
index 0000000..4e21324
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -0,0 +1,913 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.textservice.SpellCheckerInfo;
+import android.view.textservice.TextServicesManager;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * InputMethodManagerUtils contains some static methods that provides IME informations.
+ * This methods are supposed to be used in both the framework and the Settings application.
+ */
+public class InputMethodUtils {
+ public static final boolean DEBUG = false;
+ public static final int NOT_A_SUBTYPE_ID = -1;
+ public static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+ public static final String SUBTYPE_MODE_VOICE = "voice";
+ private static final String TAG = "InputMethodUtils";
+ private static final Locale ENGLISH_LOCALE = new Locale("en");
+ private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
+ private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
+ "EnabledWhenDefaultIsNotAsciiCapable";
+ private static final String TAG_ASCII_CAPABLE = "AsciiCapable";
+
+ private InputMethodUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static boolean isSystemIme(InputMethodInfo inputMethod) {
+ return (inputMethod.getServiceInfo().applicationInfo.flags
+ & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ public static boolean isSystemImeThatHasEnglishKeyboardSubtype(InputMethodInfo imi) {
+ if (!isSystemIme(imi)) {
+ return false;
+ }
+ return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage(), SUBTYPE_MODE_KEYBOARD);
+ }
+
+ private static boolean isSystemAuxilialyImeThatHashAutomaticSubtype(InputMethodInfo imi) {
+ if (!isSystemIme(imi)) {
+ return false;
+ }
+ if (!imi.isAuxiliaryIme()) {
+ return false;
+ }
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype s = imi.getSubtypeAt(i);
+ if (s.overridesImplicitlyEnabledSubtype()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
+ Context context, boolean isSystemReady, ArrayList<InputMethodInfo> imis) {
+ final ArrayList<InputMethodInfo> retval = new ArrayList<InputMethodInfo>();
+ boolean auxilialyImeAdded = false;
+ for (int i = 0; i < imis.size(); ++i) {
+ final InputMethodInfo imi = imis.get(i);
+ if (isDefaultEnabledIme(isSystemReady, imi, context)) {
+ retval.add(imi);
+ if (imi.isAuxiliaryIme()) {
+ auxilialyImeAdded = true;
+ }
+ }
+ }
+ if (auxilialyImeAdded) {
+ return retval;
+ }
+ for (int i = 0; i < imis.size(); ++i) {
+ final InputMethodInfo imi = imis.get(i);
+ if (isSystemAuxilialyImeThatHashAutomaticSubtype(imi)) {
+ retval.add(imi);
+ }
+ }
+ return retval;
+ }
+
+ // TODO: Rename isSystemDefaultImeThatHasCurrentLanguageSubtype
+ public static boolean isValidSystemDefaultIme(
+ boolean isSystemReady, InputMethodInfo imi, Context context) {
+ if (!isSystemReady) {
+ return false;
+ }
+ if (!isSystemIme(imi)) {
+ return false;
+ }
+ if (imi.getIsDefaultResourceId() != 0) {
+ try {
+ if (imi.isDefault(context) && containsSubtypeOf(
+ imi, context.getResources().getConfiguration().locale.getLanguage(),
+ null /* mode */)) {
+ return true;
+ }
+ } catch (Resources.NotFoundException ex) {
+ }
+ }
+ if (imi.getSubtypeCount() == 0) {
+ Slog.w(TAG, "Found no subtypes in a system IME: " + imi.getPackageName());
+ }
+ return false;
+ }
+
+ public static boolean isDefaultEnabledIme(
+ boolean isSystemReady, InputMethodInfo imi, Context context) {
+ return isValidSystemDefaultIme(isSystemReady, imi, context)
+ || isSystemImeThatHasEnglishKeyboardSubtype(imi);
+ }
+
+ private static boolean containsSubtypeOf(InputMethodInfo imi, String language, String mode) {
+ final int N = imi.getSubtypeCount();
+ for (int i = 0; i < N; ++i) {
+ if (!imi.getSubtypeAt(i).getLocale().startsWith(language)) {
+ continue;
+ }
+ if(!TextUtils.isEmpty(mode) && !imi.getSubtypeAt(i).getMode().equalsIgnoreCase(mode)) {
+ continue;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
+ ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ subtypes.add(imi.getSubtypeAt(i));
+ }
+ return subtypes;
+ }
+
+ public static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
+ InputMethodInfo imi, String mode) {
+ ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
+ subtypes.add(subtype);
+ }
+ }
+ return subtypes;
+ }
+
+ public static InputMethodInfo getMostApplicableDefaultIME(
+ List<InputMethodInfo> enabledImes) {
+ if (enabledImes != null && enabledImes.size() > 0) {
+ // We'd prefer to fall back on a system IME, since that is safer.
+ int i = enabledImes.size();
+ int firstFoundSystemIme = -1;
+ while (i > 0) {
+ i--;
+ final InputMethodInfo imi = enabledImes.get(i);
+ if (InputMethodUtils.isSystemImeThatHasEnglishKeyboardSubtype(imi)
+ && !imi.isAuxiliaryIme()) {
+ return imi;
+ }
+ if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi)
+ && !imi.isAuxiliaryIme()) {
+ firstFoundSystemIme = i;
+ }
+ }
+ return enabledImes.get(Math.max(firstFoundSystemIme, 0));
+ }
+ return null;
+ }
+
+ public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
+ return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
+ }
+
+ public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
+ if (imi != null) {
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ InputMethodSubtype ims = imi.getSubtypeAt(i);
+ if (subtypeHashCode == ims.hashCode()) {
+ return i;
+ }
+ }
+ }
+ return NOT_A_SUBTYPE_ID;
+ }
+
+ private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
+ Resources res, InputMethodInfo imi) {
+ final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
+ final String systemLocale = res.getConfiguration().locale.toString();
+ if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
+ final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
+ new HashMap<String, InputMethodSubtype>();
+ final int N = subtypes.size();
+ for (int i = 0; i < N; ++i) {
+ // scan overriding implicitly enabled subtypes.
+ InputMethodSubtype subtype = subtypes.get(i);
+ if (subtype.overridesImplicitlyEnabledSubtype()) {
+ final String mode = subtype.getMode();
+ if (!applicableModeAndSubtypesMap.containsKey(mode)) {
+ applicableModeAndSubtypesMap.put(mode, subtype);
+ }
+ }
+ }
+ if (applicableModeAndSubtypesMap.size() > 0) {
+ return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values());
+ }
+ for (int i = 0; i < N; ++i) {
+ final InputMethodSubtype subtype = subtypes.get(i);
+ final String locale = subtype.getLocale();
+ final String mode = subtype.getMode();
+ // When system locale starts with subtype's locale, that subtype will be applicable
+ // for system locale
+ // For instance, it's clearly applicable for cases like system locale = en_US and
+ // subtype = en, but it is not necessarily considered applicable for cases like system
+ // locale = en and subtype = en_US.
+ // We just call systemLocale.startsWith(locale) in this function because there is no
+ // need to find applicable subtypes aggressively unlike
+ // findLastResortApplicableSubtypeLocked.
+ if (systemLocale.startsWith(locale)) {
+ final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
+ // If more applicable subtypes are contained, skip.
+ if (applicableSubtype != null) {
+ if (systemLocale.equals(applicableSubtype.getLocale())) continue;
+ if (!systemLocale.equals(locale)) continue;
+ }
+ applicableModeAndSubtypesMap.put(mode, subtype);
+ }
+ }
+ final InputMethodSubtype keyboardSubtype
+ = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD);
+ final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
+ applicableModeAndSubtypesMap.values());
+ if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
+ for (int i = 0; i < N; ++i) {
+ final InputMethodSubtype subtype = subtypes.get(i);
+ final String mode = subtype.getMode();
+ if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
+ TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
+ applicableSubtypes.add(subtype);
+ }
+ }
+ }
+ if (keyboardSubtype == null) {
+ InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
+ res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
+ if (lastResortKeyboardSubtype != null) {
+ applicableSubtypes.add(lastResortKeyboardSubtype);
+ }
+ }
+ return applicableSubtypes;
+ }
+
+ private static List<InputMethodSubtype> getEnabledInputMethodSubtypeList(
+ Context context, InputMethodInfo imi, List<InputMethodSubtype> enabledSubtypes,
+ boolean allowsImplicitlySelectedSubtypes) {
+ if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
+ enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ context.getResources(), imi);
+ }
+ return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
+ }
+
+ /**
+ * If there are no selected subtypes, tries finding the most applicable one according to the
+ * given locale.
+ * @param subtypes this function will search the most applicable subtype in subtypes
+ * @param mode subtypes will be filtered by mode
+ * @param locale subtypes will be filtered by locale
+ * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
+ * it will return the first subtype matched with mode
+ * @return the most applicable subtypeId
+ */
+ public static InputMethodSubtype findLastResortApplicableSubtypeLocked(
+ Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
+ boolean canIgnoreLocaleAsLastResort) {
+ if (subtypes == null || subtypes.size() == 0) {
+ return null;
+ }
+ if (TextUtils.isEmpty(locale)) {
+ locale = res.getConfiguration().locale.toString();
+ }
+ final String language = locale.substring(0, 2);
+ boolean partialMatchFound = false;
+ InputMethodSubtype applicableSubtype = null;
+ InputMethodSubtype firstMatchedModeSubtype = null;
+ final int N = subtypes.size();
+ for (int i = 0; i < N; ++i) {
+ InputMethodSubtype subtype = subtypes.get(i);
+ final String subtypeLocale = subtype.getLocale();
+ // An applicable subtype should match "mode". If mode is null, mode will be ignored,
+ // and all subtypes with all modes can be candidates.
+ if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
+ if (firstMatchedModeSubtype == null) {
+ firstMatchedModeSubtype = subtype;
+ }
+ if (locale.equals(subtypeLocale)) {
+ // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
+ applicableSubtype = subtype;
+ break;
+ } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
+ // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
+ applicableSubtype = subtype;
+ partialMatchFound = true;
+ }
+ }
+ }
+
+ if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
+ return firstMatchedModeSubtype;
+ }
+
+ // The first subtype applicable to the system locale will be defined as the most applicable
+ // subtype.
+ if (DEBUG) {
+ if (applicableSubtype != null) {
+ Slog.d(TAG, "Applicable InputMethodSubtype was found: "
+ + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
+ }
+ }
+ return applicableSubtype;
+ }
+
+ public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
+ if (subtype == null) return true;
+ return !subtype.isAuxiliary();
+ }
+
+ public static void setNonSelectedSystemImesDisabledUntilUsed(
+ PackageManager packageManager, List<InputMethodInfo> enabledImis) {
+ if (DEBUG) {
+ Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
+ }
+ final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray(
+ com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes);
+ if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) {
+ return;
+ }
+ // Only the current spell checker should be treated as an enabled one.
+ final SpellCheckerInfo currentSpellChecker =
+ TextServicesManager.getInstance().getCurrentSpellChecker();
+ for (final String packageName : systemImesDisabledUntilUsed) {
+ if (DEBUG) {
+ Slog.d(TAG, "check " + packageName);
+ }
+ boolean enabledIme = false;
+ for (int j = 0; j < enabledImis.size(); ++j) {
+ final InputMethodInfo imi = enabledImis.get(j);
+ if (packageName.equals(imi.getPackageName())) {
+ enabledIme = true;
+ break;
+ }
+ }
+ if (enabledIme) {
+ // enabled ime. skip
+ continue;
+ }
+ if (currentSpellChecker != null
+ && packageName.equals(currentSpellChecker.getPackageName())) {
+ // enabled spell checker. skip
+ if (DEBUG) {
+ Slog.d(TAG, packageName + " is the current spell checker. skip");
+ }
+ continue;
+ }
+ ApplicationInfo ai = null;
+ try {
+ ai = packageManager.getApplicationInfo(packageName,
+ PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
+ } catch (NameNotFoundException e) {
+ Slog.w(TAG, "NameNotFoundException: " + packageName, e);
+ }
+ if (ai == null) {
+ // No app found for packageName
+ continue;
+ }
+ final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ if (!isSystemPackage) {
+ continue;
+ }
+ setDisabledUntilUsed(packageManager, packageName);
+ }
+ }
+
+ private static void setDisabledUntilUsed(PackageManager packageManager, String packageName) {
+ final int state = packageManager.getApplicationEnabledSetting(packageName);
+ if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ if (DEBUG) {
+ Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED");
+ }
+ packageManager.setApplicationEnabledSetting(packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0);
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED");
+ }
+ }
+ }
+
+ /**
+ * Utility class for putting and getting settings for InputMethod
+ * TODO: Move all putters and getters of settings to this class.
+ */
+ public static class InputMethodSettings {
+ // The string for enabled input method is saved as follows:
+ // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
+ private static final char INPUT_METHOD_SEPARATER = ':';
+ private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
+ private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
+
+ private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
+
+ private final Resources mRes;
+ private final ContentResolver mResolver;
+ private final HashMap<String, InputMethodInfo> mMethodMap;
+ private final ArrayList<InputMethodInfo> mMethodList;
+
+ private String mEnabledInputMethodsStrCache;
+ private int mCurrentUserId;
+
+ private static void buildEnabledInputMethodsSettingString(
+ StringBuilder builder, Pair<String, ArrayList<String>> pair) {
+ String id = pair.first;
+ ArrayList<String> subtypes = pair.second;
+ builder.append(id);
+ // Inputmethod and subtypes are saved in the settings as follows:
+ // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
+ for (String subtypeId: subtypes) {
+ builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
+ }
+ }
+
+ public InputMethodSettings(
+ Resources res, ContentResolver resolver,
+ HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
+ int userId) {
+ setCurrentUserId(userId);
+ mRes = res;
+ mResolver = resolver;
+ mMethodMap = methodMap;
+ mMethodList = methodList;
+ }
+
+ public void setCurrentUserId(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to " + userId);
+ }
+ // IMMS settings are kept per user, so keep track of current user
+ mCurrentUserId = userId;
+ }
+
+ public List<InputMethodInfo> getEnabledInputMethodListLocked() {
+ return createEnabledInputMethodListLocked(
+ getEnabledInputMethodsAndSubtypeListLocked());
+ }
+
+ public List<Pair<InputMethodInfo, ArrayList<String>>>
+ getEnabledInputMethodAndSubtypeHashCodeListLocked() {
+ return createEnabledInputMethodAndSubtypeHashCodeListLocked(
+ getEnabledInputMethodsAndSubtypeListLocked());
+ }
+
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
+ Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
+ List<InputMethodSubtype> enabledSubtypes =
+ getEnabledInputMethodSubtypeListLocked(imi);
+ if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
+ enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ context.getResources(), imi);
+ }
+ return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
+ }
+
+ private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
+ InputMethodInfo imi) {
+ List<Pair<String, ArrayList<String>>> imsList =
+ getEnabledInputMethodsAndSubtypeListLocked();
+ ArrayList<InputMethodSubtype> enabledSubtypes =
+ new ArrayList<InputMethodSubtype>();
+ if (imi != null) {
+ for (Pair<String, ArrayList<String>> imsPair : imsList) {
+ InputMethodInfo info = mMethodMap.get(imsPair.first);
+ if (info != null && info.getId().equals(imi.getId())) {
+ final int subtypeCount = info.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ InputMethodSubtype ims = info.getSubtypeAt(i);
+ for (String s: imsPair.second) {
+ if (String.valueOf(ims.hashCode()).equals(s)) {
+ enabledSubtypes.add(ims);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ return enabledSubtypes;
+ }
+
+ // At the initial boot, the settings for input methods are not set,
+ // so we need to enable IME in that case.
+ public void enableAllIMEsIfThereIsNoEnabledIME() {
+ if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
+ StringBuilder sb = new StringBuilder();
+ final int N = mMethodList.size();
+ for (int i = 0; i < N; i++) {
+ InputMethodInfo imi = mMethodList.get(i);
+ Slog.i(TAG, "Adding: " + imi.getId());
+ if (i > 0) sb.append(':');
+ sb.append(imi.getId());
+ }
+ putEnabledInputMethodsStr(sb.toString());
+ }
+ }
+
+ public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+ ArrayList<Pair<String, ArrayList<String>>> imsList
+ = new ArrayList<Pair<String, ArrayList<String>>>();
+ final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+ if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+ return imsList;
+ }
+ mInputMethodSplitter.setString(enabledInputMethodsStr);
+ while (mInputMethodSplitter.hasNext()) {
+ String nextImsStr = mInputMethodSplitter.next();
+ mSubtypeSplitter.setString(nextImsStr);
+ if (mSubtypeSplitter.hasNext()) {
+ ArrayList<String> subtypeHashes = new ArrayList<String>();
+ // The first element is ime id.
+ String imeId = mSubtypeSplitter.next();
+ while (mSubtypeSplitter.hasNext()) {
+ subtypeHashes.add(mSubtypeSplitter.next());
+ }
+ imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
+ }
+ }
+ return imsList;
+ }
+
+ public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
+ if (reloadInputMethodStr) {
+ getEnabledInputMethodsStr();
+ }
+ if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
+ // Add in the newly enabled input method.
+ putEnabledInputMethodsStr(id);
+ } else {
+ putEnabledInputMethodsStr(
+ mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
+ }
+ }
+
+ /**
+ * Build and put a string of EnabledInputMethods with removing specified Id.
+ * @return the specified id was removed or not.
+ */
+ public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
+ boolean isRemoved = false;
+ boolean needsAppendSeparator = false;
+ for (Pair<String, ArrayList<String>> ims: imsList) {
+ String curId = ims.first;
+ if (curId.equals(id)) {
+ // We are disabling this input method, and it is
+ // currently enabled. Skip it to remove from the
+ // new list.
+ isRemoved = true;
+ } else {
+ if (needsAppendSeparator) {
+ builder.append(INPUT_METHOD_SEPARATER);
+ } else {
+ needsAppendSeparator = true;
+ }
+ buildEnabledInputMethodsSettingString(builder, ims);
+ }
+ }
+ if (isRemoved) {
+ // Update the setting with the new list of input methods.
+ putEnabledInputMethodsStr(builder.toString());
+ }
+ return isRemoved;
+ }
+
+ private List<InputMethodInfo> createEnabledInputMethodListLocked(
+ List<Pair<String, ArrayList<String>>> imsList) {
+ final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
+ for (Pair<String, ArrayList<String>> ims: imsList) {
+ InputMethodInfo info = mMethodMap.get(ims.first);
+ if (info != null) {
+ res.add(info);
+ }
+ }
+ return res;
+ }
+
+ private List<Pair<InputMethodInfo, ArrayList<String>>>
+ createEnabledInputMethodAndSubtypeHashCodeListLocked(
+ List<Pair<String, ArrayList<String>>> imsList) {
+ final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
+ = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
+ for (Pair<String, ArrayList<String>> ims : imsList) {
+ InputMethodInfo info = mMethodMap.get(ims.first);
+ if (info != null) {
+ res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
+ }
+ }
+ return res;
+ }
+
+ private void putEnabledInputMethodsStr(String str) {
+ Settings.Secure.putStringForUser(
+ mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId);
+ mEnabledInputMethodsStrCache = str;
+ if (DEBUG) {
+ Slog.d(TAG, "putEnabledInputMethodStr: " + str);
+ }
+ }
+
+ public String getEnabledInputMethodsStr() {
+ mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
+ if (DEBUG) {
+ Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
+ + ", " + mCurrentUserId);
+ }
+ return mEnabledInputMethodsStrCache;
+ }
+
+ private void saveSubtypeHistory(
+ List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
+ StringBuilder builder = new StringBuilder();
+ boolean isImeAdded = false;
+ if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
+ builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
+ newSubtypeId);
+ isImeAdded = true;
+ }
+ for (Pair<String, String> ime: savedImes) {
+ String imeId = ime.first;
+ String subtypeId = ime.second;
+ if (TextUtils.isEmpty(subtypeId)) {
+ subtypeId = NOT_A_SUBTYPE_ID_STR;
+ }
+ if (isImeAdded) {
+ builder.append(INPUT_METHOD_SEPARATER);
+ } else {
+ isImeAdded = true;
+ }
+ builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
+ subtypeId);
+ }
+ // Remove the last INPUT_METHOD_SEPARATER
+ putSubtypeHistoryStr(builder.toString());
+ }
+
+ private void addSubtypeToHistory(String imeId, String subtypeId) {
+ List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+ for (Pair<String, String> ime: subtypeHistory) {
+ if (ime.first.equals(imeId)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
+ + ime.second);
+ }
+ // We should break here
+ subtypeHistory.remove(ime);
+ break;
+ }
+ }
+ if (DEBUG) {
+ Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
+ }
+ saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
+ }
+
+ private void putSubtypeHistoryStr(String str) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSubtypeHistoryStr: " + str);
+ }
+ Settings.Secure.putStringForUser(
+ mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId);
+ }
+
+ public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
+ // Gets the first one from the history
+ return getLastSubtypeForInputMethodLockedInternal(null);
+ }
+
+ public String getLastSubtypeForInputMethodLocked(String imeId) {
+ Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
+ if (ime != null) {
+ return ime.second;
+ } else {
+ return null;
+ }
+ }
+
+ private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
+ List<Pair<String, ArrayList<String>>> enabledImes =
+ getEnabledInputMethodsAndSubtypeListLocked();
+ List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+ for (Pair<String, String> imeAndSubtype : subtypeHistory) {
+ final String imeInTheHistory = imeAndSubtype.first;
+ // If imeId is empty, returns the first IME and subtype in the history
+ if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
+ final String subtypeInTheHistory = imeAndSubtype.second;
+ final String subtypeHashCode =
+ getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
+ enabledImes, imeInTheHistory, subtypeInTheHistory);
+ if (!TextUtils.isEmpty(subtypeHashCode)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
+ }
+ return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
+ }
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "No enabled IME found in the history");
+ }
+ return null;
+ }
+
+ private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
+ ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
+ for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
+ if (enabledIme.first.equals(imeId)) {
+ final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
+ final InputMethodInfo imi = mMethodMap.get(imeId);
+ if (explicitlyEnabledSubtypes.size() == 0) {
+ // If there are no explicitly enabled subtypes, applicable subtypes are
+ // enabled implicitly.
+ // If IME is enabled and no subtypes are enabled, applicable subtypes
+ // are enabled implicitly, so needs to treat them to be enabled.
+ if (imi != null && imi.getSubtypeCount() > 0) {
+ List<InputMethodSubtype> implicitlySelectedSubtypes =
+ getImplicitlyApplicableSubtypesLocked(mRes, imi);
+ if (implicitlySelectedSubtypes != null) {
+ final int N = implicitlySelectedSubtypes.size();
+ for (int i = 0; i < N; ++i) {
+ final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
+ if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
+ return subtypeHashCode;
+ }
+ }
+ }
+ }
+ } else {
+ for (String s: explicitlyEnabledSubtypes) {
+ if (s.equals(subtypeHashCode)) {
+ // If both imeId and subtypeId are enabled, return subtypeId.
+ try {
+ final int hashCode = Integer.valueOf(subtypeHashCode);
+ // Check whether the subtype id is valid or not
+ if (isValidSubtypeId(imi, hashCode)) {
+ return s;
+ } else {
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ } catch (NumberFormatException e) {
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ }
+ }
+ }
+ // If imeId was enabled but subtypeId was disabled.
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ }
+ // If both imeId and subtypeId are disabled, return null
+ return null;
+ }
+
+ private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
+ ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
+ final String subtypeHistoryStr = getSubtypeHistoryStr();
+ if (TextUtils.isEmpty(subtypeHistoryStr)) {
+ return imsList;
+ }
+ mInputMethodSplitter.setString(subtypeHistoryStr);
+ while (mInputMethodSplitter.hasNext()) {
+ String nextImsStr = mInputMethodSplitter.next();
+ mSubtypeSplitter.setString(nextImsStr);
+ if (mSubtypeSplitter.hasNext()) {
+ String subtypeId = NOT_A_SUBTYPE_ID_STR;
+ // The first element is ime id.
+ String imeId = mSubtypeSplitter.next();
+ while (mSubtypeSplitter.hasNext()) {
+ subtypeId = mSubtypeSplitter.next();
+ break;
+ }
+ imsList.add(new Pair<String, String>(imeId, subtypeId));
+ }
+ }
+ return imsList;
+ }
+
+ private String getSubtypeHistoryStr() {
+ if (DEBUG) {
+ Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId));
+ }
+ return Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId);
+ }
+
+ public void putSelectedInputMethod(String imeId) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
+ + mCurrentUserId);
+ }
+ Settings.Secure.putStringForUser(
+ mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId);
+ }
+
+ public void putSelectedSubtype(int subtypeId) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
+ + mCurrentUserId);
+ }
+ Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+ subtypeId, mCurrentUserId);
+ }
+
+ public String getDisabledSystemInputMethods() {
+ return Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId);
+ }
+
+ public String getSelectedInputMethod() {
+ if (DEBUG) {
+ Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId)
+ + ", " + mCurrentUserId);
+ }
+ return Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId);
+ }
+
+ public boolean isSubtypeSelected() {
+ return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
+ }
+
+ private int getSelectedInputMethodSubtypeHashCode() {
+ try {
+ return Settings.Secure.getIntForUser(
+ mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId);
+ } catch (SettingNotFoundException e) {
+ return NOT_A_SUBTYPE_ID;
+ }
+ }
+
+ public int getCurrentUserId() {
+ return mCurrentUserId;
+ }
+
+ public int getSelectedInputMethodSubtypeId(String selectedImiId) {
+ final InputMethodInfo imi = mMethodMap.get(selectedImiId);
+ if (imi == null) {
+ return NOT_A_SUBTYPE_ID;
+ }
+ final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
+ return getSubtypeIdFromHashCode(imi, subtypeHashCode);
+ }
+
+ public void saveCurrentInputMethodAndSubtypeToHistory(
+ String curMethodId, InputMethodSubtype currentSubtype) {
+ String subtypeId = NOT_A_SUBTYPE_ID_STR;
+ if (currentSubtype != null) {
+ subtypeId = String.valueOf(currentSubtype.hashCode());
+ }
+ if (canAddToLastInputMethod(currentSubtype)) {
+ addSubtypeToHistory(curMethodId, subtypeId);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index c517a68..8282d23 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -31,6 +31,7 @@ import com.android.internal.util.ProcFileReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.net.ProtocolException;
import libcore.io.IoUtils;
@@ -41,7 +42,8 @@ import libcore.io.IoUtils;
public class NetworkStatsFactory {
private static final String TAG = "NetworkStatsFactory";
- // TODO: consider moving parsing to native code
+ private static final boolean USE_NATIVE_PARSING = true;
+ private static final boolean SANITY_CHECK_NATIVE = false;
/** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
private final File mStatsXtIfaceAll;
@@ -69,7 +71,7 @@ public class NetworkStatsFactory {
*
* @throws IllegalStateException when problem parsing stats.
*/
- public NetworkStats readNetworkStatsSummaryDev() throws IllegalStateException {
+ public NetworkStats readNetworkStatsSummaryDev() throws IOException {
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
@@ -105,11 +107,9 @@ public class NetworkStatsFactory {
reader.finishLine();
}
} catch (NullPointerException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
+ throw new ProtocolException("problem parsing stats", e);
} catch (NumberFormatException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
- } catch (IOException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
+ throw new ProtocolException("problem parsing stats", e);
} finally {
IoUtils.closeQuietly(reader);
StrictMode.setThreadPolicy(savedPolicy);
@@ -124,7 +124,7 @@ public class NetworkStatsFactory {
*
* @throws IllegalStateException when problem parsing stats.
*/
- public NetworkStats readNetworkStatsSummaryXt() throws IllegalStateException {
+ public NetworkStats readNetworkStatsSummaryXt() throws IOException {
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
// return null when kernel doesn't support
@@ -154,11 +154,9 @@ public class NetworkStatsFactory {
reader.finishLine();
}
} catch (NullPointerException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
+ throw new ProtocolException("problem parsing stats", e);
} catch (NumberFormatException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
- } catch (IOException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
+ throw new ProtocolException("problem parsing stats", e);
} finally {
IoUtils.closeQuietly(reader);
StrictMode.setThreadPolicy(savedPolicy);
@@ -166,17 +164,33 @@ public class NetworkStatsFactory {
return stats;
}
- public NetworkStats readNetworkStatsDetail() {
+ public NetworkStats readNetworkStatsDetail() throws IOException {
return readNetworkStatsDetail(UID_ALL);
}
+ public NetworkStats readNetworkStatsDetail(int limitUid) throws IOException {
+ if (USE_NATIVE_PARSING) {
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
+ if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid) != 0) {
+ throw new IOException("Failed to parse network stats");
+ }
+ if (SANITY_CHECK_NATIVE) {
+ final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid);
+ assertEquals(javaStats, stats);
+ }
+ return stats;
+ } else {
+ return javaReadNetworkStatsDetail(mStatsXtUid, limitUid);
+ }
+ }
+
/**
- * Parse and return {@link NetworkStats} with UID-level details. Values
- * monotonically increase since device boot.
- *
- * @throws IllegalStateException when problem parsing stats.
+ * Parse and return {@link NetworkStats} with UID-level details. Values are
+ * expected to monotonically increase since device boot.
*/
- public NetworkStats readNetworkStatsDetail(int limitUid) throws IllegalStateException {
+ @VisibleForTesting
+ public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid)
+ throws IOException {
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
@@ -188,13 +202,13 @@ public class NetworkStatsFactory {
ProcFileReader reader = null;
try {
// open and consume header line
- reader = new ProcFileReader(new FileInputStream(mStatsXtUid));
+ reader = new ProcFileReader(new FileInputStream(detailPath));
reader.finishLine();
while (reader.hasMoreData()) {
idx = reader.nextInt();
if (idx != lastIdx + 1) {
- throw new IllegalStateException(
+ throw new ProtocolException(
"inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
}
lastIdx = idx;
@@ -215,11 +229,9 @@ public class NetworkStatsFactory {
reader.finishLine();
}
} catch (NullPointerException e) {
- throw new IllegalStateException("problem parsing idx " + idx, e);
+ throw new ProtocolException("problem parsing idx " + idx, e);
} catch (NumberFormatException e) {
- throw new IllegalStateException("problem parsing idx " + idx, e);
- } catch (IOException e) {
- throw new IllegalStateException("problem parsing idx " + idx, e);
+ throw new ProtocolException("problem parsing idx " + idx, e);
} finally {
IoUtils.closeQuietly(reader);
StrictMode.setThreadPolicy(savedPolicy);
@@ -227,4 +239,30 @@ public class NetworkStatsFactory {
return stats;
}
+
+ public void assertEquals(NetworkStats expected, NetworkStats actual) {
+ if (expected.size() != actual.size()) {
+ throw new AssertionError(
+ "Expected size " + expected.size() + ", actual size " + actual.size());
+ }
+
+ NetworkStats.Entry expectedRow = null;
+ NetworkStats.Entry actualRow = null;
+ for (int i = 0; i < expected.size(); i++) {
+ expectedRow = expected.getValues(i, expectedRow);
+ actualRow = actual.getValues(i, actualRow);
+ if (!expectedRow.equals(actualRow)) {
+ throw new AssertionError(
+ "Expected row " + i + ": " + expectedRow + ", actual row " + actualRow);
+ }
+ }
+ }
+
+ /**
+ * Parse statistics from file into given {@link NetworkStats} object. Values
+ * are expected to monotonically increase since device boot.
+ */
+ @VisibleForTesting
+ public static native int nativeReadNetworkStatsDetail(
+ NetworkStats stats, String path, int limitUid);
}
diff --git a/core/java/com/android/internal/os/BaseCommand.java b/core/java/com/android/internal/os/BaseCommand.java
new file mode 100644
index 0000000..e26b27d
--- /dev/null
+++ b/core/java/com/android/internal/os/BaseCommand.java
@@ -0,0 +1,146 @@
+/*
+**
+** Copyright 2013, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package com.android.internal.os;
+
+import java.io.PrintStream;
+
+public abstract class BaseCommand {
+
+ protected String[] mArgs;
+ private int mNextArg;
+ private String mCurArgData;
+
+ // These are magic strings understood by the Eclipse plugin.
+ public static final String FATAL_ERROR_CODE = "Error type 1";
+ public static final String NO_SYSTEM_ERROR_CODE = "Error type 2";
+ public static final String NO_CLASS_ERROR_CODE = "Error type 3";
+
+ /**
+ * Call to run the command.
+ */
+ public void run(String[] args) {
+ if (args.length < 1) {
+ onShowUsage(System.out);
+ return;
+ }
+
+ mArgs = args;
+ mNextArg = 0;
+ mCurArgData = null;
+
+ try {
+ onRun();
+ } catch (IllegalArgumentException e) {
+ onShowUsage(System.err);
+ System.err.println();
+ System.err.println("Error: " + e.getMessage());
+ } catch (Exception e) {
+ e.printStackTrace(System.err);
+ System.exit(1);
+ }
+ }
+
+ /**
+ * Convenience to show usage information to error output.
+ */
+ public void showUsage() {
+ onShowUsage(System.err);
+ }
+
+ /**
+ * Convenience to show usage information to error output along
+ * with an error message.
+ */
+ public void showError(String message) {
+ onShowUsage(System.err);
+ System.err.println();
+ System.err.println(message);
+ }
+
+ /**
+ * Implement the command.
+ */
+ public abstract void onRun() throws Exception;
+
+ /**
+ * Print help text for the command.
+ */
+ public abstract void onShowUsage(PrintStream out);
+
+ /**
+ * Return the next option on the command line -- that is an argument that
+ * starts with '-'. If the next argument is not an option, null is returned.
+ */
+ public String nextOption() {
+ if (mCurArgData != null) {
+ String prev = mArgs[mNextArg - 1];
+ throw new IllegalArgumentException("No argument expected after \"" + prev + "\"");
+ }
+ if (mNextArg >= mArgs.length) {
+ return null;
+ }
+ String arg = mArgs[mNextArg];
+ if (!arg.startsWith("-")) {
+ return null;
+ }
+ mNextArg++;
+ if (arg.equals("--")) {
+ return null;
+ }
+ if (arg.length() > 1 && arg.charAt(1) != '-') {
+ if (arg.length() > 2) {
+ mCurArgData = arg.substring(2);
+ return arg.substring(0, 2);
+ } else {
+ mCurArgData = null;
+ return arg;
+ }
+ }
+ mCurArgData = null;
+ return arg;
+ }
+
+ /**
+ * Return the next argument on the command line, whatever it is; if there are
+ * no arguments left, return null.
+ */
+ public String nextArg() {
+ if (mCurArgData != null) {
+ String arg = mCurArgData;
+ mCurArgData = null;
+ return arg;
+ } else if (mNextArg < mArgs.length) {
+ return mArgs[mNextArg++];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Return the next argument on the command line, whatever it is; if there are
+ * no arguments left, throws an IllegalArgumentException to report this to the user.
+ */
+ public String nextArgRequired() {
+ String arg = nextArg();
+ if (arg == null) {
+ String prev = mArgs[mNextArg - 1];
+ throw new IllegalArgumentException("Argument expected after \"" + prev + "\"");
+ }
+ return arg;
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 94e7a06..6375dbe 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,14 +16,11 @@
package com.android.internal.os;
-import static android.net.NetworkStats.IFACE_ALL;
-import static android.net.NetworkStats.UID_ALL;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
-import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkStats;
import android.os.BatteryManager;
@@ -49,7 +46,6 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
-import com.android.internal.R;
import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.util.JournaledFile;
import com.google.android.collect.Sets;
@@ -87,7 +83,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 62 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 64 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -98,11 +94,7 @@ public final class BatteryStatsImpl extends BatteryStats {
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
// in to one common name.
- private static final int MAX_WAKELOCKS_PER_UID = 30;
-
- // The system process gets more. It is special. Oh so special.
- // With, you know, special needs. Like this.
- private static final int MAX_WAKELOCKS_PER_UID_IN_SYSTEM = 50;
+ private static final int MAX_WAKELOCKS_PER_UID = 50;
private static final String BATCHED_WAKELOCK_NAME = "*overflow*";
@@ -356,8 +348,8 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public static interface Unpluggable {
- void unplug(long batteryUptime, long batteryRealtime);
- void plug(long batteryUptime, long batteryRealtime);
+ void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime);
+ void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime);
}
/**
@@ -392,12 +384,12 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mUnpluggedCount);
}
- public void unplug(long batteryUptime, long batteryRealtime) {
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
mUnpluggedCount = mPluggedCount;
mCount.set(mPluggedCount);
}
- public void plug(long batteryUptime, long batteryRealtime) {
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
mPluggedCount = mCount.get();
}
@@ -587,7 +579,7 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(mUnpluggedTime);
}
- public void unplug(long batteryUptime, long batteryRealtime) {
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
if (DEBUG && mType < 0) {
Log.v(TAG, "unplug #" + mType + ": realtime=" + batteryRealtime
+ " old mUnpluggedTime=" + mUnpluggedTime
@@ -602,7 +594,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public void plug(long batteryUptime, long batteryRealtime) {
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
if (DEBUG && mType < 0) {
Log.v(TAG, "plug #" + mType + ": realtime=" + batteryRealtime
+ " old mTotalTime=" + mTotalTime);
@@ -731,7 +723,7 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mTrackingReportedValues;
/*
- * A sequnce counter, incremented once for each update of the stats.
+ * A sequence counter, incremented once for each update of the stats.
*/
int mUpdateVersion;
@@ -786,8 +778,8 @@ public final class BatteryStatsImpl extends BatteryStats {
mCurrentReportedTotalTime = totalTime;
}
- public void unplug(long batteryUptime, long batteryRealtime) {
- super.unplug(batteryUptime, batteryRealtime);
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ super.unplug(elapsedRealtime, batteryUptime, batteryRealtime);
if (mTrackingReportedValues) {
mUnpluggedReportedTotalTime = mCurrentReportedTotalTime;
mUnpluggedReportedCount = mCurrentReportedCount;
@@ -795,8 +787,8 @@ public final class BatteryStatsImpl extends BatteryStats {
mInDischarge = true;
}
- public void plug(long batteryUptime, long batteryRealtime) {
- super.plug(batteryUptime, batteryRealtime);
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
mInDischarge = false;
}
@@ -849,6 +841,141 @@ public final class BatteryStatsImpl extends BatteryStats {
}
/**
+ * A timer that increments in batches. It does not run for durations, but just jumps
+ * for a pre-determined amount.
+ */
+ public static final class BatchTimer extends Timer {
+ final Uid mUid;
+
+ /**
+ * The last time at which we updated the timer. This is in elapsed realtime microseconds.
+ */
+ long mLastAddedTime;
+
+ /**
+ * The last duration that we added to the timer. This is in microseconds.
+ */
+ long mLastAddedDuration;
+
+ /**
+ * Whether we are currently in a discharge cycle.
+ */
+ boolean mInDischarge;
+
+ BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables,
+ boolean inDischarge, Parcel in) {
+ super(type, unpluggables, in);
+ mUid = uid;
+ mLastAddedTime = in.readLong();
+ mLastAddedDuration = in.readLong();
+ mInDischarge = inDischarge;
+ }
+
+ BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables,
+ boolean inDischarge) {
+ super(type, unpluggables);
+ mUid = uid;
+ mInDischarge = inDischarge;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, long batteryRealtime) {
+ super.writeToParcel(out, batteryRealtime);
+ out.writeLong(mLastAddedTime);
+ out.writeLong(mLastAddedDuration);
+ }
+
+ @Override
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ recomputeLastDuration(SystemClock.elapsedRealtime() * 1000, false);
+ mInDischarge = false;
+ super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
+ }
+
+ @Override
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ recomputeLastDuration(elapsedRealtime, false);
+ mInDischarge = true;
+ // If we are still within the last added duration, then re-added whatever remains.
+ if (mLastAddedTime == elapsedRealtime) {
+ mTotalTime += mLastAddedDuration;
+ }
+ super.unplug(elapsedRealtime, batteryUptime, batteryRealtime);
+ }
+
+ @Override
+ public void logState(Printer pw, String prefix) {
+ super.logState(pw, prefix);
+ pw.println(prefix + "mLastAddedTime=" + mLastAddedTime
+ + " mLastAddedDuration=" + mLastAddedDuration);
+ }
+
+ private long computeOverage(long curTime) {
+ if (mLastAddedTime > 0) {
+ return mLastTime + mLastAddedDuration - curTime;
+ }
+ return 0;
+ }
+
+ private void recomputeLastDuration(long curTime, boolean abort) {
+ final long overage = computeOverage(curTime);
+ if (overage > 0) {
+ // Aborting before the duration ran out -- roll back the remaining
+ // duration. Only do this if currently discharging; otherwise we didn't
+ // actually add the time.
+ if (mInDischarge) {
+ mTotalTime -= overage;
+ }
+ if (abort) {
+ mLastAddedTime = 0;
+ } else {
+ mLastAddedTime = curTime;
+ mLastAddedDuration -= overage;
+ }
+ }
+ }
+
+ public void addDuration(BatteryStatsImpl stats, long durationMillis) {
+ final long now = SystemClock.elapsedRealtime() * 1000;
+ recomputeLastDuration(now, true);
+ mLastAddedTime = now;
+ mLastAddedDuration = durationMillis * 1000;
+ if (mInDischarge) {
+ mTotalTime += mLastAddedDuration;
+ mCount++;
+ }
+ }
+
+ public void abortLastDuration(BatteryStatsImpl stats) {
+ final long now = SystemClock.elapsedRealtime() * 1000;
+ recomputeLastDuration(now, true);
+ }
+
+ @Override
+ protected int computeCurrentCountLocked() {
+ return mCount;
+ }
+
+ @Override
+ protected long computeRunTimeLocked(long curBatteryRealtime) {
+ final long overage = computeOverage(SystemClock.elapsedRealtime() * 1000);
+ if (overage > 0) {
+ return mTotalTime = overage;
+ }
+ return mTotalTime;
+ }
+
+ @Override
+ boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+ final long now = SystemClock.elapsedRealtime() * 1000;
+ recomputeLastDuration(now, true);
+ boolean stillActive = mLastAddedTime == now;
+ super.reset(stats, !stillActive && detachIfReset);
+ return !stillActive;
+ }
+ }
+
+ /**
* State for keeping track of timing information.
*/
public static final class StopwatchTimer extends Timer {
@@ -902,12 +1029,12 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(mUpdateTime);
}
- public void plug(long batteryUptime, long batteryRealtime) {
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
if (mNesting > 0) {
if (DEBUG && mType < 0) {
Log.v(TAG, "old mUpdateTime=" + mUpdateTime);
}
- super.plug(batteryUptime, batteryRealtime);
+ super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
mUpdateTime = batteryRealtime;
if (DEBUG && mType < 0) {
Log.v(TAG, "new mUpdateTime=" + mUpdateTime);
@@ -1443,7 +1570,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mHistoryOverflow = false;
}
- public void doUnplugLocked(long batteryUptime, long batteryRealtime) {
+ public void doUnplugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
NetworkStats.Entry entry = null;
// Track UID data usage
@@ -1452,9 +1579,7 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i = 0; i < size; i++) {
entry = uidStats.getValues(i, entry);
- final Uid u = mUidStats.get(entry.uid);
- if (u == null) continue;
-
+ final Uid u = getUidStatsLocked(entry.uid);
u.mStartedTcpBytesReceived = entry.rxBytes;
u.mStartedTcpBytesSent = entry.txBytes;
u.mTcpBytesReceivedAtLastUnplug = u.mCurrentTcpBytesReceived;
@@ -1462,7 +1587,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
- mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
+ mUnpluggables.get(i).unplug(elapsedRealtime, batteryUptime, batteryRealtime);
}
// Track both mobile and total overall data
@@ -1483,7 +1608,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mBluetoothPingCount = 0;
}
- public void doPlugLocked(long batteryUptime, long batteryRealtime) {
+ public void doPlugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
NetworkStats.Entry entry = null;
for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
@@ -1498,7 +1623,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
- mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
+ mUnpluggables.get(i).plug(elapsedRealtime, batteryUptime, batteryRealtime);
}
// Track both mobile and total overall data
@@ -2109,6 +2234,14 @@ public final class BatteryStatsImpl extends BatteryStats {
getUidStatsLocked(uid).noteVideoTurnedOffLocked();
}
+ public void noteVibratorOnLocked(int uid, long durationMillis) {
+ getUidStatsLocked(uid).noteVibratorOnLocked(durationMillis);
+ }
+
+ public void noteVibratorOffLocked(int uid) {
+ getUidStatsLocked(uid).noteVibratorOffLocked();
+ }
+
public void noteWifiRunningLocked(WorkSource ws) {
if (!mGlobalWifiRunning) {
mHistoryCur.states |= HistoryItem.STATE_WIFI_RUNNING_FLAG;
@@ -2402,6 +2535,8 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mVideoTurnedOn;
StopwatchTimer mVideoTurnedOnTimer;
+ BatchTimer mVibratorOnTimer;
+
Counter[] mUserActivityCounters;
/**
@@ -2439,10 +2574,6 @@ public final class BatteryStatsImpl extends BatteryStats {
mWifiScanTimers, mUnpluggables);
mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
mWifiMulticastTimers, mUnpluggables);
- mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
- null, mUnpluggables);
- mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
- null, mUnpluggables);
}
@Override
@@ -2587,15 +2718,19 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ public StopwatchTimer createAudioTurnedOnTimerLocked() {
+ if (mAudioTurnedOnTimer == null) {
+ mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
+ null, mUnpluggables);
+ }
+ return mAudioTurnedOnTimer;
+ }
+
@Override
public void noteAudioTurnedOnLocked() {
if (!mAudioTurnedOn) {
mAudioTurnedOn = true;
- if (mAudioTurnedOnTimer == null) {
- mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
- null, mUnpluggables);
- }
- mAudioTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
+ createAudioTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this);
}
}
@@ -2603,19 +2738,25 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteAudioTurnedOffLocked() {
if (mAudioTurnedOn) {
mAudioTurnedOn = false;
- mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ if (mAudioTurnedOnTimer != null) {
+ mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ }
+ }
+ }
+
+ public StopwatchTimer createVideoTurnedOnTimerLocked() {
+ if (mVideoTurnedOnTimer == null) {
+ mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
+ null, mUnpluggables);
}
+ return mVideoTurnedOnTimer;
}
@Override
public void noteVideoTurnedOnLocked() {
if (!mVideoTurnedOn) {
mVideoTurnedOn = true;
- if (mVideoTurnedOnTimer == null) {
- mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
- null, mUnpluggables);
- }
- mVideoTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
+ createVideoTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this);
}
}
@@ -2623,7 +2764,27 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteVideoTurnedOffLocked() {
if (mVideoTurnedOn) {
mVideoTurnedOn = false;
- mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ if (mVideoTurnedOnTimer != null) {
+ mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ }
+ }
+ }
+
+ public BatchTimer createVibratorOnTimerLocked() {
+ if (mVibratorOnTimer == null) {
+ mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON,
+ mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal);
+ }
+ return mVibratorOnTimer;
+ }
+
+ public void noteVibratorOnLocked(long durationMillis) {
+ createVibratorOnTimerLocked().addDuration(BatteryStatsImpl.this, durationMillis);
+ }
+
+ public void noteVibratorOffLocked() {
+ if (mVibratorOnTimer != null) {
+ mVibratorOnTimer.abortLastDuration(BatteryStatsImpl.this);
}
}
@@ -2677,6 +2838,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public Timer getVibratorOnTimer() {
+ return mVibratorOnTimer;
+ }
+
+ @Override
public void noteUserActivityLocked(int type) {
if (mUserActivityCounters == null) {
initUserActivityLocked();
@@ -2747,6 +2913,14 @@ public final class BatteryStatsImpl extends BatteryStats {
active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false);
active |= mVideoTurnedOn;
}
+ if (mVibratorOnTimer != null) {
+ if (mVibratorOnTimer.reset(BatteryStatsImpl.this, false)) {
+ mVibratorOnTimer.detach();
+ mVibratorOnTimer = null;
+ } else {
+ active = true;
+ }
+ }
mLoadedTcpBytesReceived = mLoadedTcpBytesSent = 0;
mCurrentTcpBytesReceived = mCurrentTcpBytesSent = 0;
@@ -2832,9 +3006,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (mAudioTurnedOnTimer != null) {
mAudioTurnedOnTimer.detach();
+ mAudioTurnedOnTimer = null;
}
if (mVideoTurnedOnTimer != null) {
mVideoTurnedOnTimer.detach();
+ mVideoTurnedOnTimer = null;
}
if (mUserActivityCounters != null) {
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
@@ -2917,6 +3093,12 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
+ if (mVibratorOnTimer != null) {
+ out.writeInt(1);
+ mVibratorOnTimer.writeToParcel(out, batteryRealtime);
+ } else {
+ out.writeInt(0);
+ }
if (mUserActivityCounters != null) {
out.writeInt(1);
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
@@ -3016,6 +3198,12 @@ public final class BatteryStatsImpl extends BatteryStats {
mVideoTurnedOnTimer = null;
}
if (in.readInt() != 0) {
+ mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON,
+ mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal, in);
+ } else {
+ mVibratorOnTimer = null;
+ }
+ if (in.readInt() != 0) {
mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES];
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
mUserActivityCounters[i] = new Counter(mUnpluggables, in);
@@ -3256,14 +3444,14 @@ public final class BatteryStatsImpl extends BatteryStats {
mSpeedBins = new SamplingCounter[getCpuSpeedSteps()];
}
- public void unplug(long batteryUptime, long batteryRealtime) {
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
mUnpluggedUserTime = mUserTime;
mUnpluggedSystemTime = mSystemTime;
mUnpluggedStarts = mStarts;
mUnpluggedForegroundTime = mForegroundTime;
}
- public void plug(long batteryUptime, long batteryRealtime) {
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
}
void detach() {
@@ -3550,11 +3738,11 @@ public final class BatteryStatsImpl extends BatteryStats {
mUnpluggables.add(this);
}
- public void unplug(long batteryUptime, long batteryRealtime) {
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
mUnpluggedWakeups = mWakeups;
}
- public void plug(long batteryUptime, long batteryRealtime) {
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
}
void detach() {
@@ -3712,13 +3900,13 @@ public final class BatteryStatsImpl extends BatteryStats {
mUnpluggables.add(this);
}
- public void unplug(long batteryUptime, long batteryRealtime) {
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
mUnpluggedStartTime = getStartTimeToNowLocked(batteryUptime);
mUnpluggedStarts = mStarts;
mUnpluggedLaunches = mLaunches;
}
- public void plug(long batteryUptime, long batteryRealtime) {
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
}
void detach() {
@@ -3942,8 +4130,7 @@ public final class BatteryStatsImpl extends BatteryStats {
Wakelock wl = mWakelockStats.get(name);
if (wl == null) {
final int N = mWakelockStats.size();
- if (N > MAX_WAKELOCKS_PER_UID && (mUid != Process.SYSTEM_UID
- || N > MAX_WAKELOCKS_PER_UID_IN_SYSTEM)) {
+ if (N > MAX_WAKELOCKS_PER_UID) {
name = BATCHED_WAKELOCK_NAME;
wl = mWakelockStats.get(name);
}
@@ -4367,7 +4554,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
mDischargeAmountScreenOn = 0;
mDischargeAmountScreenOff = 0;
- doUnplugLocked(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
+ doUnplugLocked(realtime, mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
} else {
updateKernelWakelocksLocked();
mHistoryCur.batteryLevel = (byte)level;
@@ -4383,7 +4570,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
}
updateDischargeScreenLevelsLocked(mScreenOn, mScreenOn);
- doPlugLocked(getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
+ doPlugLocked(realtime, getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
}
if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
if (mFile != null) {
@@ -5161,11 +5348,14 @@ public final class BatteryStatsImpl extends BatteryStats {
}
u.mAudioTurnedOn = false;
if (in.readInt() != 0) {
- u.mAudioTurnedOnTimer.readSummaryFromParcelLocked(in);
+ u.createAudioTurnedOnTimerLocked().readSummaryFromParcelLocked(in);
}
u.mVideoTurnedOn = false;
if (in.readInt() != 0) {
- u.mVideoTurnedOnTimer.readSummaryFromParcelLocked(in);
+ u.createVideoTurnedOnTimerLocked().readSummaryFromParcelLocked(in);
+ }
+ if (in.readInt() != 0) {
+ u.createVibratorOnTimerLocked().readSummaryFromParcelLocked(in);
}
if (in.readInt() != 0) {
@@ -5367,6 +5557,12 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
+ if (u.mVibratorOnTimer != null) {
+ out.writeInt(1);
+ u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ } else {
+ out.writeInt(0);
+ }
if (u.mUserActivityCounters == null) {
out.writeInt(0);
@@ -5753,7 +5949,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (SystemProperties.getBoolean(PROP_QTAGUID_ENABLED, false)) {
try {
mNetworkSummaryCache = mNetworkStatsFactory.readNetworkStatsSummaryDev();
- } catch (IllegalStateException e) {
+ } catch (IOException e) {
Log.wtf(TAG, "problem reading network stats", e);
}
}
@@ -5777,7 +5973,7 @@ public final class BatteryStatsImpl extends BatteryStats {
try {
mNetworkDetailCache = mNetworkStatsFactory
.readNetworkStatsDetail().groupedByUid();
- } catch (IllegalStateException e) {
+ } catch (IOException e) {
Log.wtf(TAG, "problem reading network stats", e);
}
}
diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java
index 88e58dc..6fb72f1 100644
--- a/core/java/com/android/internal/os/SomeArgs.java
+++ b/core/java/com/android/internal/os/SomeArgs.java
@@ -39,6 +39,7 @@ public final class SomeArgs {
public Object arg2;
public Object arg3;
public Object arg4;
+ public Object arg5;
public int argi1;
public int argi2;
public int argi3;
@@ -85,6 +86,7 @@ public final class SomeArgs {
arg2 = null;
arg3 = null;
arg4 = null;
+ arg5 = null;
argi1 = 0;
argi2 = 0;
argi3 = 0;
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index d24513a..fd7e3b0 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -76,18 +76,6 @@ class ZygoteConnection {
private final String peerSecurityContext;
/**
- * A long-lived reference to the original command socket used to launch
- * this peer. If "peer wait" mode is specified, the process that requested
- * the new VM instance intends to track the lifetime of the spawned instance
- * via the command socket. In this case, the command socket is closed
- * in the Zygote and placed here in the spawned instance so that it will
- * not be collected and finalized. This field remains null at all times
- * in the original Zygote process, and in all spawned processes where
- * "peer-wait" mode was not requested.
- */
- private static LocalSocket sPeerWaitSocket = null;
-
- /**
* Constructs instance from connected socket.
*
* @param socket non-null; connected socket
@@ -298,11 +286,6 @@ class ZygoteConnection {
* <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call.
* <code>r</code> is the resource, <code>c</code> and <code>m</code>
* are the settings for current and max value.</i>
- * <li> --peer-wait indicates that the command socket should
- * be inherited by (and set to close-on-exec in) the spawned process
- * and used to track the lifetime of that process. The spawning process
- * then exits. Without this flag, it is retained by the spawning process
- * (and closed in the child) in expectation of a new spawn request.
* <li> --classpath=<i>colon-separated classpath</i> indicates
* that the specified class (which must b first non-flag argument) should
* be loaded from jar files in the specified classpath. Incompatible with
@@ -330,9 +313,6 @@ class ZygoteConnection {
/** from --setgroups */
int[] gids;
- /** from --peer-wait */
- boolean peerWait;
-
/**
* From --enable-debugger, --enable-checkjni, --enable-assert,
* --enable-safemode, and --enable-jni-logging.
@@ -437,8 +417,6 @@ class ZygoteConnection {
debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING;
} else if (arg.equals("--enable-assert")) {
debugFlags |= Zygote.DEBUG_ENABLE_ASSERT;
- } else if (arg.equals("--peer-wait")) {
- peerWait = true;
} else if (arg.equals("--runtime-init")) {
runtimeInit = true;
} else if (arg.startsWith("--seinfo=")) {
@@ -897,23 +875,8 @@ class ZygoteConnection {
FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
throws ZygoteInit.MethodAndArgsCaller {
- /*
- * Close the socket, unless we're in "peer wait" mode, in which
- * case it's used to track the liveness of this process.
- */
-
- if (parsedArgs.peerWait) {
- try {
- ZygoteInit.setCloseOnExec(mSocket.getFileDescriptor(), true);
- sPeerWaitSocket = mSocket;
- } catch (IOException ex) {
- Log.e(TAG, "Zygote Child: error setting peer wait "
- + "socket to be close-on-exec", ex);
- }
- } else {
- closeSocket();
- ZygoteInit.closeServerSocket();
- }
+ closeSocket();
+ ZygoteInit.closeServerSocket();
if (descriptors != null) {
try {
@@ -1044,18 +1007,6 @@ class ZygoteConnection {
return true;
}
- /*
- * If the peer wants to use the socket to wait on the
- * newly spawned process, then we're all done.
- */
- if (parsedArgs.peerWait) {
- try {
- mSocket.close();
- } catch (IOException ex) {
- Log.e(TAG, "Zygote: error closing sockets", ex);
- }
- return true;
- }
return false;
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 9e43749..2184fd2 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -19,14 +19,13 @@ package com.android.internal.os;
import static libcore.io.OsConstants.S_IRWXG;
import static libcore.io.OsConstants.S_IRWXO;
-import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.net.LocalServerSocket;
import android.os.Debug;
import android.os.Process;
import android.os.SystemClock;
+import android.os.Trace;
import android.util.EventLog;
import android.util.Log;
@@ -88,12 +87,6 @@ public class ZygoteInit {
static final int GC_LOOP_COUNT = 10;
/**
- * If true, zygote forks for each peer. If false, a select loop is used
- * inside a single process. The latter is preferred.
- */
- private static final boolean ZYGOTE_FORK_MODE = false;
-
- /**
* The name of a resource file that contains classes to preload.
*/
private static final String PRELOADED_CLASSES = "preloaded-classes";
@@ -536,6 +529,10 @@ public class ZygoteInit {
// Do an initial gc to clean up after startup
gc();
+ // Disable tracing so that forked processes do not inherit stale tracing tags from
+ // Zygote.
+ Trace.setTracingEnabled(false);
+
// If requested, start system server directly from Zygote
if (argv.length != 2) {
throw new RuntimeException(argv[0] + USAGE_STRING);
@@ -549,11 +546,7 @@ public class ZygoteInit {
Log.i(TAG, "Accepting command socket connections");
- if (ZYGOTE_FORK_MODE) {
- runForkMode();
- } else {
- runSelectLoopMode();
- }
+ runSelectLoop();
closeServerSocket();
} catch (MethodAndArgsCaller caller) {
@@ -566,44 +559,6 @@ public class ZygoteInit {
}
/**
- * Runs the zygote in accept-and-fork mode. In this mode, each peer
- * gets its own zygote spawner process. This code is retained for
- * reference only.
- *
- * @throws MethodAndArgsCaller in a child process when a main() should
- * be executed.
- */
- private static void runForkMode() throws MethodAndArgsCaller {
- while (true) {
- ZygoteConnection peer = acceptCommandPeer();
-
- int pid;
-
- pid = Zygote.fork();
-
- if (pid == 0) {
- // The child process should handle the peer requests
-
- // The child does not accept any more connections
- try {
- sServerSocket.close();
- } catch (IOException ex) {
- Log.e(TAG, "Zygote Child: error closing sockets", ex);
- } finally {
- sServerSocket = null;
- }
-
- peer.run();
- break;
- } else if (pid > 0) {
- peer.closeSocket();
- } else {
- throw new RuntimeException("Error invoking fork()");
- }
- }
- }
-
- /**
* Runs the zygote process's select loop. Accepts new connections as
* they happen, and reads commands from connections one spawn-request's
* worth at a time.
@@ -611,9 +566,9 @@ public class ZygoteInit {
* @throws MethodAndArgsCaller in a child process when a main() should
* be executed.
*/
- private static void runSelectLoopMode() throws MethodAndArgsCaller {
- ArrayList<FileDescriptor> fds = new ArrayList();
- ArrayList<ZygoteConnection> peers = new ArrayList();
+ private static void runSelectLoop() throws MethodAndArgsCaller {
+ ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
+ ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
FileDescriptor[] fdArray = new FileDescriptor[4];
fds.add(sServerSocket.getFileDescriptor());
@@ -734,17 +689,6 @@ public class ZygoteInit {
throws IOException;
/**
- * Sets the permitted and effective capability sets of this process.
- *
- * @param permittedCapabilities permitted set
- * @param effectiveCapabilities effective set
- * @throws IOException on error
- */
- static native void setCapabilities(
- long permittedCapabilities,
- long effectiveCapabilities) throws IOException;
-
- /**
* Invokes select() on the provider array of file descriptors (selecting
* for readability only). Array elements of null are ignored.
*
diff --git a/core/java/com/android/internal/policy/PolicyManager.java b/core/java/com/android/internal/policy/PolicyManager.java
index 5274e54..462b3a9 100644
--- a/core/java/com/android/internal/policy/PolicyManager.java
+++ b/core/java/com/android/internal/policy/PolicyManager.java
@@ -22,8 +22,6 @@ import android.view.LayoutInflater;
import android.view.Window;
import android.view.WindowManagerPolicy;
-import com.android.internal.policy.IPolicy;
-
/**
* {@hide}
*/
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 780f5b3..58b15e2 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -17,7 +17,7 @@
package com.android.internal.statusbar;
import com.android.internal.statusbar.StatusBarIcon;
-import com.android.internal.statusbar.StatusBarNotification;
+import android.service.notification.StatusBarNotification;
/** @hide */
oneway interface IStatusBar
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 04e5bc9..c98ba8d 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -19,7 +19,7 @@ package com.android.internal.statusbar;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarIconList;
-import com.android.internal.statusbar.StatusBarNotification;
+import android.service.notification.StatusBarNotification;
/** @hide */
interface IStatusBarService
diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.java b/core/java/com/android/internal/statusbar/StatusBarNotification.java
deleted file mode 100644
index a91aa3c..0000000
--- a/core/java/com/android/internal/statusbar/StatusBarNotification.java
+++ /dev/null
@@ -1,149 +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 com.android.internal.statusbar;
-
-import android.app.Notification;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.UserHandle;
-
-/*
-boolean clearable = !n.ongoingEvent && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0);
-
-
-// TODO: make this restriction do something smarter like never fill
-// more than two screens. "Why would anyone need more than 80 characters." :-/
-final int maxTickerLen = 80;
-if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) {
- truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen);
-}
-*/
-
-/**
- * Class encapsulating a Notification. Sent by the NotificationManagerService to the IStatusBar (in System UI).
- */
-public class StatusBarNotification implements Parcelable {
- 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, UserHandle user) {
- if (pkg == null) throw new NullPointerException();
- if (notification == null) throw new NullPointerException();
-
- this.pkg = pkg;
- this.id = id;
- this.tag = tag;
- this.uid = uid;
- this.initialPid = initialPid;
- this.score = score;
- this.notification = notification;
- this.user = user;
- this.notification.setUser(user);
- }
-
- public StatusBarNotification(Parcel in) {
- this.pkg = in.readString();
- this.id = in.readInt();
- if (in.readInt() != 0) {
- this.tag = in.readString();
- } else {
- this.tag = null;
- }
- this.uid = in.readInt();
- 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) {
- out.writeString(this.pkg);
- out.writeInt(this.id);
- if (this.tag != null) {
- out.writeInt(1);
- out.writeString(this.tag);
- } else {
- out.writeInt(0);
- }
- out.writeInt(this.uid);
- out.writeInt(this.initialPid);
- out.writeInt(this.score);
- this.notification.writeToParcel(out, flags);
- user.writeToParcel(out, flags);
- }
-
- public int describeContents() {
- return 0;
- }
-
- public static final Parcelable.Creator<StatusBarNotification> CREATOR
- = new Parcelable.Creator<StatusBarNotification>()
- {
- public StatusBarNotification createFromParcel(Parcel parcel)
- {
- return new StatusBarNotification(parcel);
- }
-
- public StatusBarNotification[] newArray(int size)
- {
- return new StatusBarNotification[size];
- }
- };
-
- @Override
- public StatusBarNotification 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 + " user=" + user + ")";
- }
-
- public boolean isOngoing() {
- return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
- }
-
- public boolean isClearable() {
- 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/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index dbf6c8e..9137d3c 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -79,6 +79,10 @@ public class ArrayUtils
* @return true if they're equal, false otherwise
*/
public static boolean equals(byte[] array1, byte[] array2, int length) {
+ if (length < 0) {
+ throw new IllegalArgumentException();
+ }
+
if (array1 == array2) {
return true;
}
@@ -123,14 +127,34 @@ public class ArrayUtils
* @return true if the value is present in the array
*/
public static <T> boolean contains(T[] array, T value) {
- for (T element : array) {
- if (element == null) {
- if (value == null) return true;
+ return indexOf(array, value) != -1;
+ }
+
+ /**
+ * Return first index of {@code value} in {@code array}, or {@code -1} if
+ * not found.
+ */
+ public static <T> int indexOf(T[] array, T value) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == null) {
+ if (value == null) return i;
} else {
- if (value != null && element.equals(value)) return true;
+ if (value != null && array[i].equals(value)) return i;
}
}
- return false;
+ return -1;
+ }
+
+ /**
+ * Test if all {@code check} items are contained in {@code array}.
+ */
+ public static <T> boolean containsAll(T[] array, T[] check) {
+ for (T checkItem : check) {
+ if (!contains(array, checkItem)) {
+ return false;
+ }
+ }
+ return true;
}
public static boolean contains(int[] array, int value) {
diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java
index 592a8fa..7a04080 100644
--- a/core/java/com/android/internal/util/FastXmlSerializer.java
+++ b/core/java/com/android/internal/util/FastXmlSerializer.java
@@ -50,6 +50,8 @@ public class FastXmlSerializer implements XmlSerializer {
private static final int BUFFER_LEN = 8192;
+ private static String sSpace = " ";
+
private final char[] mText = new char[BUFFER_LEN];
private int mPos;
@@ -59,8 +61,12 @@ public class FastXmlSerializer implements XmlSerializer {
private CharsetEncoder mCharset;
private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN);
+ private boolean mIndent = false;
private boolean mInTag;
+ private int mNesting = 0;
+ private boolean mLineStart = true;
+
private void append(char c) throws IOException {
int pos = mPos;
if (pos >= (BUFFER_LEN-1)) {
@@ -113,6 +119,14 @@ public class FastXmlSerializer implements XmlSerializer {
append(str, 0, str.length());
}
+ private void appendIndent(int indent) throws IOException {
+ indent *= 4;
+ if (indent > sSpace.length()) {
+ indent = sSpace.length();
+ }
+ append(sSpace, 0, indent);
+ }
+
private void escapeAndAppendString(final String string) throws IOException {
final int N = string.length();
final char NE = (char)ESCAPE_TABLE.length;
@@ -161,6 +175,7 @@ public class FastXmlSerializer implements XmlSerializer {
escapeAndAppendString(value);
append('"');
+ mLineStart = false;
return this;
}
@@ -185,9 +200,13 @@ public class FastXmlSerializer implements XmlSerializer {
public XmlSerializer endTag(String namespace, String name) throws IOException,
IllegalArgumentException, IllegalStateException {
+ mNesting--;
if (mInTag) {
append(" />\n");
} else {
+ if (mIndent && mLineStart) {
+ appendIndent(mNesting);
+ }
append("</");
if (namespace != null) {
append(namespace);
@@ -196,6 +215,7 @@ public class FastXmlSerializer implements XmlSerializer {
append(name);
append(">\n");
}
+ mLineStart = true;
mInTag = false;
return this;
}
@@ -278,6 +298,7 @@ public class FastXmlSerializer implements XmlSerializer {
public void setFeature(String name, boolean state) throws IllegalArgumentException,
IllegalStateException {
if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) {
+ mIndent = true;
return;
}
throw new UnsupportedOperationException();
@@ -325,6 +346,7 @@ public class FastXmlSerializer implements XmlSerializer {
IllegalArgumentException, IllegalStateException {
append("<?xml version='1.0' encoding='utf-8' standalone='"
+ (standalone ? "yes" : "no") + "' ?>\n");
+ mLineStart = true;
}
public XmlSerializer startTag(String namespace, String name) throws IOException,
@@ -332,6 +354,10 @@ public class FastXmlSerializer implements XmlSerializer {
if (mInTag) {
append(">\n");
}
+ if (mIndent) {
+ appendIndent(mNesting);
+ }
+ mNesting++;
append('<');
if (namespace != null) {
append(namespace);
@@ -339,6 +365,7 @@ public class FastXmlSerializer implements XmlSerializer {
}
append(name);
mInTag = true;
+ mLineStart = false;
return this;
}
@@ -349,6 +376,9 @@ public class FastXmlSerializer implements XmlSerializer {
mInTag = false;
}
escapeAndAppendString(buf, start, len);
+ if (mIndent) {
+ mLineStart = buf[start+len-1] == '\n';
+ }
return this;
}
@@ -359,6 +389,9 @@ public class FastXmlSerializer implements XmlSerializer {
mInTag = false;
}
escapeAndAppendString(text);
+ if (mIndent) {
+ mLineStart = text.length() > 0 && (text.charAt(text.length()-1) == '\n');
+ }
return this;
}
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index dd5918b..d01a817 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -21,29 +21,47 @@ import java.io.Writer;
/**
* Lightweight wrapper around {@link PrintWriter} that automatically indents
- * newlines based on internal state. Delays writing indent until first actual
- * write on a newline, enabling indent modification after newline.
+ * newlines based on internal state. It also automatically wraps long lines
+ * based on given line length.
+ * <p>
+ * Delays writing indent until first actual write on a newline, enabling indent
+ * modification after newline.
*/
public class IndentingPrintWriter extends PrintWriter {
- private final String mIndent;
+ private final String mSingleIndent;
+ private final int mWrapLength;
- private StringBuilder mBuilder = new StringBuilder();
- private char[] mCurrent;
+ /** Mutable version of current indent */
+ private StringBuilder mIndentBuilder = new StringBuilder();
+ /** Cache of current {@link #mIndentBuilder} value */
+ private char[] mCurrentIndent;
+ /** Length of current line being built, excluding any indent */
+ private int mCurrentLength;
+
+ /**
+ * Flag indicating if we're currently sitting on an empty line, and that
+ * next write should be prefixed with the current indent.
+ */
private boolean mEmptyLine = true;
- public IndentingPrintWriter(Writer writer, String indent) {
+ public IndentingPrintWriter(Writer writer, String singleIndent) {
+ this(writer, singleIndent, -1);
+ }
+
+ public IndentingPrintWriter(Writer writer, String singleIndent, int wrapLength) {
super(writer);
- mIndent = indent;
+ mSingleIndent = singleIndent;
+ mWrapLength = wrapLength;
}
public void increaseIndent() {
- mBuilder.append(mIndent);
- mCurrent = null;
+ mIndentBuilder.append(mSingleIndent);
+ mCurrentIndent = null;
}
public void decreaseIndent() {
- mBuilder.delete(0, mIndent.length());
- mCurrent = null;
+ mIndentBuilder.delete(0, mSingleIndent.length());
+ mCurrentIndent = null;
}
public void printPair(String key, Object value) {
@@ -52,33 +70,56 @@ public class IndentingPrintWriter extends PrintWriter {
@Override
public void write(char[] buf, int offset, int count) {
+ final int indentLength = mIndentBuilder.length();
final int bufferEnd = offset + count;
int lineStart = offset;
int lineEnd = offset;
+
+ // March through incoming buffer looking for newlines
while (lineEnd < bufferEnd) {
char ch = buf[lineEnd++];
+ mCurrentLength++;
if (ch == '\n') {
- writeIndent();
+ maybeWriteIndent();
super.write(buf, lineStart, lineEnd - lineStart);
lineStart = lineEnd;
mEmptyLine = true;
+ mCurrentLength = 0;
+ }
+
+ // Wrap if we've pushed beyond line length
+ if (mWrapLength > 0 && mCurrentLength >= mWrapLength - indentLength) {
+ if (!mEmptyLine) {
+ // Give ourselves a fresh line to work with
+ super.write('\n');
+ mEmptyLine = true;
+ mCurrentLength = lineEnd - lineStart;
+ } else {
+ // We need more than a dedicated line, slice it hard
+ maybeWriteIndent();
+ super.write(buf, lineStart, lineEnd - lineStart);
+ super.write('\n');
+ mEmptyLine = true;
+ lineStart = lineEnd;
+ mCurrentLength = 0;
+ }
}
}
if (lineStart != lineEnd) {
- writeIndent();
+ maybeWriteIndent();
super.write(buf, lineStart, lineEnd - lineStart);
}
}
- private void writeIndent() {
+ private void maybeWriteIndent() {
if (mEmptyLine) {
mEmptyLine = false;
- if (mBuilder.length() != 0) {
- if (mCurrent == null) {
- mCurrent = mBuilder.toString().toCharArray();
+ if (mIndentBuilder.length() != 0) {
+ if (mCurrentIndent == null) {
+ mCurrentIndent = mIndentBuilder.toString().toCharArray();
}
- super.write(mCurrent, 0, mCurrent.length);
+ super.write(mCurrentIndent, 0, mCurrentIndent.length);
}
}
}
diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java
index 72e1f0f..81571fe 100644
--- a/core/java/com/android/internal/util/ProcFileReader.java
+++ b/core/java/com/android/internal/util/ProcFileReader.java
@@ -19,6 +19,7 @@ package com.android.internal.util;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
+import java.net.ProtocolException;
import java.nio.charset.Charsets;
/**
@@ -82,12 +83,15 @@ public class ProcFileReader implements Closeable {
}
/**
- * Find buffer index of next token delimiter, usually space or newline. Will
- * fill buffer as needed.
+ * Find buffer index of next token delimiter, usually space or newline.
+ * Fills buffer as needed.
+ *
+ * @return Index of next delimeter, otherwise -1 if no tokens remain on
+ * current line.
*/
private int nextTokenIndex() throws IOException {
if (mLineFinished) {
- throw new IOException("no tokens remaining on current line");
+ return -1;
}
int i = 0;
@@ -105,7 +109,7 @@ public class ProcFileReader implements Closeable {
}
} while (fillBuf() > 0);
- throw new IOException("end of stream while looking for token boundary");
+ throw new ProtocolException("End of stream while looking for token boundary");
}
/**
@@ -136,7 +140,7 @@ public class ProcFileReader implements Closeable {
}
} while (fillBuf() > 0);
- throw new IOException("end of stream while looking for line boundary");
+ throw new ProtocolException("End of stream while looking for line boundary");
}
/**
@@ -144,9 +148,11 @@ public class ProcFileReader implements Closeable {
*/
public String nextString() throws IOException {
final int tokenIndex = nextTokenIndex();
- final String s = new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII);
- consumeBuf(tokenIndex + 1);
- return s;
+ if (tokenIndex == -1) {
+ throw new ProtocolException("Missing required string");
+ } else {
+ return parseAndConsumeString(tokenIndex);
+ }
}
/**
@@ -154,6 +160,33 @@ public class ProcFileReader implements Closeable {
*/
public long nextLong() throws IOException {
final int tokenIndex = nextTokenIndex();
+ if (tokenIndex == -1) {
+ throw new ProtocolException("Missing required long");
+ } else {
+ return parseAndConsumeLong(tokenIndex);
+ }
+ }
+
+ /**
+ * Parse and return next token as base-10 encoded {@code long}, or return
+ * the given default value if no remaining tokens on current line.
+ */
+ public long nextOptionalLong(long def) throws IOException {
+ final int tokenIndex = nextTokenIndex();
+ if (tokenIndex == -1) {
+ return def;
+ } else {
+ return parseAndConsumeLong(tokenIndex);
+ }
+ }
+
+ private String parseAndConsumeString(int tokenIndex) throws IOException {
+ final String s = new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII);
+ consumeBuf(tokenIndex + 1);
+ return s;
+ }
+
+ private long parseAndConsumeLong(int tokenIndex) throws IOException {
final boolean negative = mBuffer[0] == '-';
// TODO: refactor into something like IntegralToString
@@ -193,6 +226,7 @@ public class ProcFileReader implements Closeable {
return (int) value;
}
+ @Override
public void close() throws IOException {
mStream.close();
}
diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java
index 7c2b1b5..b380403 100644
--- a/core/java/com/android/internal/util/Protocol.java
+++ b/core/java/com/android/internal/util/Protocol.java
@@ -45,11 +45,13 @@ public class Protocol {
public static final int BASE_WIFI_P2P_SERVICE = 0x00023000;
public static final int BASE_WIFI_MONITOR = 0x00024000;
public static final int BASE_WIFI_MANAGER = 0x00025000;
+ public static final int BASE_WIFI_CONTROLLER = 0x00026000;
public static final int BASE_DHCP = 0x00030000;
public static final int BASE_DATA_CONNECTION = 0x00040000;
public static final int BASE_DATA_CONNECTION_AC = 0x00041000;
public static final int BASE_DATA_CONNECTION_TRACKER = 0x00042000;
public static final int BASE_DNS_PINGER = 0x00050000;
public static final int BASE_NSD_MANAGER = 0x00060000;
+ public static final int BASE_NETWORK_STATE_TRACKER = 0x00070000;
//TODO: define all used protocols
}
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 0ea7b83..d26f79e 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -27,6 +27,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Vector;
@@ -35,7 +36,7 @@ import java.util.Vector;
*
* <p>The state machine defined here is a hierarchical state machine which processes messages
* and can have states arranged hierarchically.</p>
- *
+ *
* <p>A state is a <code>State</code> object and must implement
* <code>processMessage</code> and optionally <code>enter/exit/getName</code>.
* The enter/exit methods are equivalent to the construction and destruction
@@ -81,8 +82,8 @@ import java.util.Vector;
* machine will cause <code>haltedProcessMessage</code> to be invoked.</p>
*
* <p>If it is desirable to completely stop the state machine call <code>quit</code> or
- * <code>abort</code>. These will call <code>exit</code> of the current state and its parents, call
- * <code>onQuiting</code> and then exit Thread/Loopers.</p>
+ * <code>quitNow</code>. These will call <code>exit</code> of the current state and its parents,
+ * call <code>onQuiting</code> and then exit Thread/Loopers.</p>
*
* <p>In addition to <code>processMessage</code> each <code>State</code> has
* an <code>enter</code> method and <code>exit</exit> method which may be overridden.</p>
@@ -148,7 +149,7 @@ class HelloWorld extends StateMachine {
class State1 extends State {
&#64;Override public boolean processMessage(Message message) {
- Log.d(TAG, "Hello World");
+ log("Hello World");
return HANDLED;
}
}
@@ -232,8 +233,6 @@ state mP2 {
* <p>The implementation is below and also in StateMachineTest:</p>
<code>
class Hsm1 extends StateMachine {
- private static final String TAG = "hsm1";
-
public static final int CMD_1 = 1;
public static final int CMD_2 = 2;
public static final int CMD_3 = 3;
@@ -241,16 +240,16 @@ class Hsm1 extends StateMachine {
public static final int CMD_5 = 5;
public static Hsm1 makeHsm1() {
- Log.d(TAG, "makeHsm1 E");
+ log("makeHsm1 E");
Hsm1 sm = new Hsm1("hsm1");
sm.start();
- Log.d(TAG, "makeHsm1 X");
+ log("makeHsm1 X");
return sm;
}
Hsm1(String name) {
super(name);
- Log.d(TAG, "ctor E");
+ log("ctor E");
// Add states, use indentation to show hierarchy
addState(mP1);
@@ -260,16 +259,16 @@ class Hsm1 extends StateMachine {
// Set the initial state
setInitialState(mS1);
- Log.d(TAG, "ctor X");
+ log("ctor X");
}
class P1 extends State {
&#64;Override public void enter() {
- Log.d(TAG, "mP1.enter");
+ log("mP1.enter");
}
&#64;Override public boolean processMessage(Message message) {
boolean retVal;
- Log.d(TAG, "mP1.processMessage what=" + message.what);
+ log("mP1.processMessage what=" + message.what);
switch(message.what) {
case CMD_2:
// CMD_2 will arrive in mS2 before CMD_3
@@ -286,16 +285,16 @@ class Hsm1 extends StateMachine {
return retVal;
}
&#64;Override public void exit() {
- Log.d(TAG, "mP1.exit");
+ log("mP1.exit");
}
}
class S1 extends State {
&#64;Override public void enter() {
- Log.d(TAG, "mS1.enter");
+ log("mS1.enter");
}
&#64;Override public boolean processMessage(Message message) {
- Log.d(TAG, "S1.processMessage what=" + message.what);
+ log("S1.processMessage what=" + message.what);
if (message.what == CMD_1) {
// Transition to ourself to show that enter/exit is called
transitionTo(mS1);
@@ -306,17 +305,17 @@ class Hsm1 extends StateMachine {
}
}
&#64;Override public void exit() {
- Log.d(TAG, "mS1.exit");
+ log("mS1.exit");
}
}
class S2 extends State {
&#64;Override public void enter() {
- Log.d(TAG, "mS2.enter");
+ log("mS2.enter");
}
&#64;Override public boolean processMessage(Message message) {
boolean retVal;
- Log.d(TAG, "mS2.processMessage what=" + message.what);
+ log("mS2.processMessage what=" + message.what);
switch(message.what) {
case(CMD_2):
sendMessage(obtainMessage(CMD_4));
@@ -334,17 +333,17 @@ class Hsm1 extends StateMachine {
return retVal;
}
&#64;Override public void exit() {
- Log.d(TAG, "mS2.exit");
+ log("mS2.exit");
}
}
class P2 extends State {
&#64;Override public void enter() {
- Log.d(TAG, "mP2.enter");
+ log("mP2.enter");
sendMessage(obtainMessage(CMD_5));
}
&#64;Override public boolean processMessage(Message message) {
- Log.d(TAG, "P2.processMessage what=" + message.what);
+ log("P2.processMessage what=" + message.what);
switch(message.what) {
case(CMD_3):
break;
@@ -357,13 +356,13 @@ class Hsm1 extends StateMachine {
return HANDLED;
}
&#64;Override public void exit() {
- Log.d(TAG, "mP2.exit");
+ log("mP2.exit");
}
}
&#64;Override
void onHalting() {
- Log.d(TAG, "halting");
+ log("halting");
synchronized (this) {
this.notifyAll();
}
@@ -386,7 +385,7 @@ synchronize(hsm) {
// wait for the messages to be handled
hsm.wait();
} catch (InterruptedException e) {
- Log.e(TAG, "exception while waiting " + e.getMessage());
+ loge("exception while waiting " + e.getMessage());
}
}
</code>
@@ -418,8 +417,7 @@ D/hsm1 ( 1999): halting
</code>
*/
public class StateMachine {
-
- private static final String TAG = "StateMachine";
+ // Name of the state machine and used as logging tag
private String mName;
/** Message.what value when quitting */
@@ -447,36 +445,44 @@ public class StateMachine {
* {@hide}
*/
public static class LogRec {
+ private StateMachine mSm;
private long mTime;
private int mWhat;
private String mInfo;
- private State mState;
- private State mOrgState;
+ private IState mState;
+ private IState mOrgState;
+ private IState mDstState;
/**
* Constructor
*
* @param msg
- * @param state that handled the message
+ * @param state the state which handled the message
* @param orgState is the first state the received the message but
* did not processes the message.
+ * @param transToState is the state that was transitioned to after the message was
+ * processed.
*/
- LogRec(Message msg, String info, State state, State orgState) {
- update(msg, info, state, orgState);
+ LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState,
+ IState transToState) {
+ update(sm, msg, info, state, orgState, transToState);
}
/**
* Update the information in the record.
* @param state that handled the message
- * @param orgState is the first state the received the message but
- * did not processes the message.
+ * @param orgState is the first state the received the message
+ * @param dstState is the state that was the transition target when logging
*/
- public void update(Message msg, String info, State state, State orgState) {
+ public void update(StateMachine sm, Message msg, String info, IState state, IState orgState,
+ IState dstState) {
+ mSm = sm;
mTime = System.currentTimeMillis();
mWhat = (msg != null) ? msg.what : 0;
mInfo = info;
mState = state;
mOrgState = orgState;
+ mDstState = dstState;
}
/**
@@ -503,32 +509,39 @@ public class StateMachine {
/**
* @return the state that handled this message
*/
- public State getState() {
+ public IState getState() {
return mState;
}
/**
- * @return the original state that received the message.
+ * @return the state destination state if a transition is occurring or null if none.
*/
- public State getOriginalState() {
- return mOrgState;
+ public IState getDestState() {
+ return mDstState;
}
/**
- * @return as string
+ * @return the original state that received the message.
*/
- public String toString(StateMachine sm) {
+ public IState getOriginalState() {
+ return mOrgState;
+ }
+
+ @Override
+ public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("time=");
Calendar c = Calendar.getInstance();
c.setTimeInMillis(mTime);
sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
- sb.append(" state=");
+ sb.append(" processed=");
sb.append(mState == null ? "<null>" : mState.getName());
- sb.append(" orgState=");
+ sb.append(" org=");
sb.append(mOrgState == null ? "<null>" : mOrgState.getName());
+ sb.append(" dest=");
+ sb.append(mDstState == null ? "<null>" : mDstState.getName());
sb.append(" what=");
- String what = sm.getWhatToString(mWhat);
+ String what = mSm != null ? mSm.getWhatToString(mWhat) : "";
if (TextUtils.isEmpty(what)) {
sb.append(mWhat);
sb.append("(0x");
@@ -537,7 +550,7 @@ public class StateMachine {
} else {
sb.append(what);
}
- if ( ! TextUtils.isEmpty(mInfo)) {
+ if (!TextUtils.isEmpty(mInfo)) {
sb.append(" ");
sb.append(mInfo);
}
@@ -560,10 +573,11 @@ public class StateMachine {
private static final int DEFAULT_SIZE = 20;
- private Vector<LogRec> mLogRecords = new Vector<LogRec>();
+ private Vector<LogRec> mLogRecVector = new Vector<LogRec>();
private int mMaxSize = DEFAULT_SIZE;
private int mOldestIndex = 0;
private int mCount = 0;
+ private boolean mLogOnlyTransitions = false;
/**
* private constructor use add
@@ -579,14 +593,22 @@ public class StateMachine {
synchronized void setSize(int maxSize) {
mMaxSize = maxSize;
mCount = 0;
- mLogRecords.clear();
+ mLogRecVector.clear();
+ }
+
+ synchronized void setLogOnlyTransitions(boolean enable) {
+ mLogOnlyTransitions = enable;
+ }
+
+ synchronized boolean logOnlyTransitions() {
+ return mLogOnlyTransitions;
}
/**
* @return the number of recent records.
*/
synchronized int size() {
- return mLogRecords.size();
+ return mLogRecVector.size();
}
/**
@@ -600,7 +622,7 @@ public class StateMachine {
* Clear the list of records.
*/
synchronized void cleanup() {
- mLogRecords.clear();
+ mLogRecVector.clear();
}
/**
@@ -616,7 +638,7 @@ public class StateMachine {
if (nextIndex >= size()) {
return null;
} else {
- return mLogRecords.get(nextIndex);
+ return mLogRecVector.get(nextIndex);
}
}
@@ -628,25 +650,31 @@ public class StateMachine {
* @param state that handled the message
* @param orgState is the first state the received the message but
* did not processes the message.
+ * @param transToState is the state that was transitioned to after the message was
+ * processed.
+ *
*/
- synchronized void add(Message msg, String messageInfo, State state, State orgState) {
+ synchronized void add(StateMachine sm, Message msg, String messageInfo, IState state,
+ IState orgState, IState transToState) {
mCount += 1;
- if (mLogRecords.size() < mMaxSize) {
- mLogRecords.add(new LogRec(msg, messageInfo, state, orgState));
+ if (mLogRecVector.size() < mMaxSize) {
+ mLogRecVector.add(new LogRec(sm, msg, messageInfo, state, orgState, transToState));
} else {
- LogRec pmi = mLogRecords.get(mOldestIndex);
+ LogRec pmi = mLogRecVector.get(mOldestIndex);
mOldestIndex += 1;
if (mOldestIndex >= mMaxSize) {
mOldestIndex = 0;
}
- pmi.update(msg, messageInfo, state, orgState);
+ pmi.update(sm, msg, messageInfo, state, orgState, transToState);
}
}
}
-
private static class SmHandler extends Handler {
+ /** true if StateMachine has quit */
+ private boolean mHasQuit = false;
+
/** The debug flag */
private boolean mDbg = false;
@@ -702,15 +730,13 @@ public class StateMachine {
*/
@Override
public String toString() {
- return "state=" + state.getName() + ",active=" + active
- + ",parent=" + ((parentStateInfo == null) ?
- "null" : parentStateInfo.state.getName());
+ return "state=" + state.getName() + ",active=" + active + ",parent="
+ + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName());
}
}
/** The map of all of the states in the state machine */
- private HashMap<State, StateInfo> mStateInfo =
- new HashMap<State, StateInfo>();
+ private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();
/** The initial state that will process the first message */
private State mInitialState;
@@ -750,66 +776,99 @@ public class StateMachine {
*/
@Override
public final void handleMessage(Message msg) {
- if (mDbg) Log.d(TAG, "handleMessage: E msg.what=" + msg.what);
-
- /** Save the current message */
- mMsg = msg;
-
- if (mIsConstructionCompleted) {
- /** Normal path */
- processMsg(msg);
- } else if (!mIsConstructionCompleted &&
- (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {
- /** Initial one time path. */
- mIsConstructionCompleted = true;
- invokeEnterMethods(0);
- } else {
- throw new RuntimeException("StateMachine.handleMessage: " +
- "The start method not called, received msg: " + msg);
- }
- performTransitions();
+ if (!mHasQuit) {
+ if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
+
+ /** Save the current message */
+ mMsg = msg;
+
+ /** State that processed the message */
+ State msgProcessedState = null;
+ if (mIsConstructionCompleted) {
+ /** Normal path */
+ msgProcessedState = processMsg(msg);
+ } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
+ && (mMsg.obj == mSmHandlerObj)) {
+ /** Initial one time path. */
+ mIsConstructionCompleted = true;
+ invokeEnterMethods(0);
+ } else {
+ throw new RuntimeException("StateMachine.handleMessage: "
+ + "The start method not called, received msg: " + msg);
+ }
+ performTransitions(msgProcessedState, msg);
- if (mDbg) Log.d(TAG, "handleMessage: X");
+ // We need to check if mSm == null here as we could be quitting.
+ if (mDbg && mSm != null) mSm.log("handleMessage: X");
+ }
}
/**
* Do any transitions
+ * @param msgProcessedState is the state that processed the message
*/
- private void performTransitions() {
+ private void performTransitions(State msgProcessedState, Message msg) {
/**
* If transitionTo has been called, exit and then enter
* the appropriate states. We loop on this to allow
* enter and exit methods to use transitionTo.
*/
- State destState = null;
- while (mDestState != null) {
- if (mDbg) Log.d(TAG, "handleMessage: new destination call exit");
+ State orgState = mStateStack[mStateStackTopIndex].state;
- /**
- * Save mDestState locally and set to null
- * to know if enter/exit use transitionTo.
- */
- destState = mDestState;
- mDestState = null;
+ /**
+ * Record whether message needs to be logged before we transition and
+ * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which
+ * always set msg.obj to the handler.
+ */
+ boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj);
+
+ if (mLogRecords.logOnlyTransitions()) {
+ /** Record only if there is a transition */
+ if (mDestState != null) {
+ mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState,
+ orgState, mDestState);
+ }
+ } else if (recordLogMsg) {
+ /** Record message */
+ mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState,
+ mDestState);
+ }
+ State destState = mDestState;
+ if (destState != null) {
/**
- * Determine the states to exit and enter and return the
- * common ancestor state of the enter/exit states. Then
- * invoke the exit methods then the enter methods.
+ * Process the transitions including transitions in the enter/exit methods
*/
- StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
- invokeExitMethods(commonStateInfo);
- int stateStackEnteringIndex = moveTempStateStackToStateStack();
- invokeEnterMethods(stateStackEnteringIndex);
+ while (true) {
+ if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
+ /**
+ * Determine the states to exit and enter and return the
+ * common ancestor state of the enter/exit states. Then
+ * invoke the exit methods then the enter methods.
+ */
+ StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
+ invokeExitMethods(commonStateInfo);
+ int stateStackEnteringIndex = moveTempStateStackToStateStack();
+ invokeEnterMethods(stateStackEnteringIndex);
- /**
- * Since we have transitioned to a new state we need to have
- * any deferred messages moved to the front of the message queue
- * so they will be processed before any other messages in the
- * message queue.
- */
- moveDeferredMessageAtFrontOfQueue();
+ /**
+ * Since we have transitioned to a new state we need to have
+ * any deferred messages moved to the front of the message queue
+ * so they will be processed before any other messages in the
+ * message queue.
+ */
+ moveDeferredMessageAtFrontOfQueue();
+
+ if (destState != mDestState) {
+ // A new mDestState so continue looping
+ destState = mDestState;
+ } else {
+ // No change in mDestState so we're done
+ break;
+ }
+ }
+ mDestState = null;
}
/**
@@ -854,13 +913,14 @@ public class StateMachine {
mInitialState = null;
mDestState = null;
mDeferredMessages.clear();
+ mHasQuit = true;
}
/**
* Complete the construction of the state machine.
*/
private final void completeConstruction() {
- if (mDbg) Log.d(TAG, "completeConstruction: E");
+ if (mDbg) mSm.log("completeConstruction: E");
/**
* Determine the maximum depth of the state hierarchy
@@ -876,7 +936,7 @@ public class StateMachine {
maxDepth = depth;
}
}
- if (mDbg) Log.d(TAG, "completeConstruction: maxDepth=" + maxDepth);
+ if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth);
mStateStack = new StateInfo[maxDepth];
mTempStateStack = new StateInfo[maxDepth];
@@ -885,18 +945,19 @@ public class StateMachine {
/** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
- if (mDbg) Log.d(TAG, "completeConstruction: X");
+ if (mDbg) mSm.log("completeConstruction: X");
}
/**
* Process the message. If the current state doesn't handle
* it, call the states parent and so on. If it is never handled then
* call the state machines unhandledMessage method.
+ * @return the state that processed the message
*/
- private final void processMsg(Message msg) {
+ private final State processMsg(Message msg) {
StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
if (mDbg) {
- Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
+ mSm.log("processMsg: " + curStateInfo.state.getName());
}
if (isQuit(msg)) {
@@ -915,23 +976,11 @@ public class StateMachine {
break;
}
if (mDbg) {
- Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
- }
- }
-
- /**
- * Record that we processed the message
- */
- if (mSm.recordLogRec(msg)) {
- if (curStateInfo != null) {
- State orgState = mStateStack[mStateStackTopIndex].state;
- mLogRecords.add(msg, mSm.getLogRecString(msg), curStateInfo.state,
- orgState);
- } else {
- mLogRecords.add(msg, mSm.getLogRecString(msg), null, null);
+ mSm.log("processMsg: " + curStateInfo.state.getName());
}
}
}
+ return (curStateInfo != null) ? curStateInfo.state : null;
}
/**
@@ -939,10 +988,10 @@ public class StateMachine {
* up to the common ancestor state.
*/
private final void invokeExitMethods(StateInfo commonStateInfo) {
- while ((mStateStackTopIndex >= 0) &&
- (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
+ while ((mStateStackTopIndex >= 0)
+ && (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
State curState = mStateStack[mStateStackTopIndex].state;
- if (mDbg) Log.d(TAG, "invokeExitMethods: " + curState.getName());
+ if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());
curState.exit();
mStateStack[mStateStackTopIndex].active = false;
mStateStackTopIndex -= 1;
@@ -954,7 +1003,7 @@ public class StateMachine {
*/
private final void invokeEnterMethods(int stateStackEnteringIndex) {
for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
- if (mDbg) Log.d(TAG, "invokeEnterMethods: " + mStateStack[i].state.getName());
+ if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
mStateStack[i].state.enter();
mStateStack[i].active = true;
}
@@ -970,9 +1019,9 @@ public class StateMachine {
* as the most resent message and end with the oldest
* messages at the front of the queue.
*/
- for (int i = mDeferredMessages.size() - 1; i >= 0; i-- ) {
+ for (int i = mDeferredMessages.size() - 1; i >= 0; i--) {
Message curMsg = mDeferredMessages.get(i);
- if (mDbg) Log.d(TAG, "moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
+ if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
sendMessageAtFrontOfQueue(curMsg);
}
mDeferredMessages.clear();
@@ -990,7 +1039,7 @@ public class StateMachine {
int i = mTempStateStackCount - 1;
int j = startingIndex;
while (i >= 0) {
- if (mDbg) Log.d(TAG, "moveTempStackToStateStack: i=" + i + ",j=" + j);
+ if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j);
mStateStack[j] = mTempStateStack[i];
j += 1;
i -= 1;
@@ -998,9 +1047,9 @@ public class StateMachine {
mStateStackTopIndex = j - 1;
if (mDbg) {
- Log.d(TAG, "moveTempStackToStateStack: X mStateStackTop="
- + mStateStackTopIndex + ",startingIndex=" + startingIndex
- + ",Top=" + mStateStack[mStateStackTopIndex].state.getName());
+ mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex
+ + ",startingIndex=" + startingIndex + ",Top="
+ + mStateStack[mStateStackTopIndex].state.getName());
}
return startingIndex;
}
@@ -1031,8 +1080,8 @@ public class StateMachine {
} while ((curStateInfo != null) && !curStateInfo.active);
if (mDbg) {
- Log.d(TAG, "setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
- + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
+ mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
+ + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
}
return curStateInfo;
}
@@ -1042,8 +1091,7 @@ public class StateMachine {
*/
private final void setupInitialStateStack() {
if (mDbg) {
- Log.d(TAG, "setupInitialStateStack: E mInitialState="
- + mInitialState.getName());
+ mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
}
StateInfo curStateInfo = mStateInfo.get(mInitialState);
@@ -1083,8 +1131,8 @@ public class StateMachine {
*/
private final StateInfo addState(State state, State parent) {
if (mDbg) {
- Log.d(TAG, "addStateInternal: E state=" + state.getName()
- + ",parent=" + ((parent == null) ? "" : parent.getName()));
+ mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
+ + ((parent == null) ? "" : parent.getName()));
}
StateInfo parentStateInfo = null;
if (parent != null) {
@@ -1101,14 +1149,14 @@ public class StateMachine {
}
// Validate that we aren't adding the same state in two different hierarchies.
- if ((stateInfo.parentStateInfo != null) &&
- (stateInfo.parentStateInfo != parentStateInfo)) {
- throw new RuntimeException("state already added");
+ if ((stateInfo.parentStateInfo != null)
+ && (stateInfo.parentStateInfo != parentStateInfo)) {
+ throw new RuntimeException("state already added");
}
stateInfo.state = state;
stateInfo.parentStateInfo = parentStateInfo;
stateInfo.active = false;
- if (mDbg) Log.d(TAG, "addStateInternal: X stateInfo: " + stateInfo);
+ if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
return stateInfo;
}
@@ -1128,19 +1176,19 @@ public class StateMachine {
/** @see StateMachine#setInitialState(State) */
private final void setInitialState(State initialState) {
- if (mDbg) Log.d(TAG, "setInitialState: initialState=" + initialState.getName());
+ if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName());
mInitialState = initialState;
}
/** @see StateMachine#transitionTo(IState) */
private final void transitionTo(IState destState) {
mDestState = (State) destState;
- if (mDbg) Log.d(TAG, "transitionTo: destState=" + mDestState.getName());
+ if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName());
}
/** @see StateMachine#deferMessage(Message) */
private final void deferMessage(Message msg) {
- if (mDbg) Log.d(TAG, "deferMessage: msg=" + msg.what);
+ if (mDbg) mSm.log("deferMessage: msg=" + msg.what);
/* Copy the "msg" to "newMsg" as "msg" will be recycled */
Message newMsg = obtainMessage();
@@ -1151,17 +1199,17 @@ public class StateMachine {
/** @see StateMachine#quit() */
private final void quit() {
- if (mDbg) Log.d(TAG, "quit:");
+ if (mDbg) mSm.log("quit:");
sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
}
/** @see StateMachine#quitNow() */
private final void quitNow() {
- if (mDbg) Log.d(TAG, "abort:");
+ if (mDbg) mSm.log("quitNow:");
sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
}
- /** Validate that the message was sent by quit or abort. */
+ /** Validate that the message was sent by quit or quitNow. */
private final boolean isQuit(Message msg) {
return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj);
}
@@ -1215,6 +1263,15 @@ public class StateMachine {
}
/**
+ * Constructor creates a StateMachine using the handler.
+ *
+ * @param name of the state machine
+ */
+ protected StateMachine(String name, Handler handler) {
+ initStateMachine(name, handler.getLooper());
+ }
+
+ /**
* Add a new state to the state machine
* @param state the state to add
* @param parent the parent of state
@@ -1224,20 +1281,6 @@ public class StateMachine {
}
/**
- * @return current message
- */
- protected final Message getCurrentMessage() {
- return mSmHandler.getCurrentMessage();
- }
-
- /**
- * @return current state
- */
- protected final IState getCurrentState() {
- return mSmHandler.getCurrentState();
- }
-
- /**
* Add a new state to the state machine, parent will be null
* @param state to add
*/
@@ -1256,6 +1299,26 @@ public class StateMachine {
}
/**
+ * @return current message
+ */
+ protected final Message getCurrentMessage() {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return null;
+ return smh.getCurrentMessage();
+ }
+
+ /**
+ * @return current state
+ */
+ protected final IState getCurrentState() {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return null;
+ return smh.getCurrentState();
+ }
+
+ /**
* transition to destination state. Upon returning
* from processMessage the current state's exit will
* be executed and upon the next message arriving
@@ -1303,7 +1366,7 @@ public class StateMachine {
* @param msg that couldn't be handled.
*/
protected void unhandledMessage(Message msg) {
- if (mSmHandler.mDbg) Log.e(TAG, mName + " - unhandledMessage: msg.what=" + msg.what);
+ if (mSmHandler.mDbg) loge(" - unhandledMessage: msg.what=" + msg.what);
}
/**
@@ -1347,43 +1410,69 @@ public class StateMachine {
}
/**
+ * Set to log only messages that cause a state transition
+ *
+ * @param enable {@code true} to enable, {@code false} to disable
+ */
+ public final void setLogOnlyTransitions(boolean enable) {
+ mSmHandler.mLogRecords.setLogOnlyTransitions(enable);
+ }
+
+ /**
* @return number of log records
*/
public final int getLogRecSize() {
- return mSmHandler.mLogRecords.size();
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return 0;
+ return smh.mLogRecords.size();
}
/**
* @return the total number of records processed
*/
public final int getLogRecCount() {
- return mSmHandler.mLogRecords.count();
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return 0;
+ return smh.mLogRecords.count();
}
/**
- * @return a log record
+ * @return a log record, or null if index is out of range
*/
public final LogRec getLogRec(int index) {
- return mSmHandler.mLogRecords.get(index);
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return null;
+ return smh.mLogRecords.get(index);
}
/**
- * Add the string to LogRecords.
- *
- * @param string
+ * @return a copy of LogRecs as a collection
*/
- protected void addLogRec(String string) {
- mSmHandler.mLogRecords.add(null, string, null, null);
+ public final Collection<LogRec> copyLogRecs() {
+ Vector<LogRec> vlr = new Vector<LogRec>();
+ SmHandler smh = mSmHandler;
+ if (smh != null) {
+ for (LogRec lr : smh.mLogRecords.mLogRecVector) {
+ vlr.add(lr);
+ }
+ }
+ return vlr;
}
/**
- * Add the string and state to LogRecords
+ * Add the string to LogRecords.
*
* @param string
- * @param state current state
*/
- protected void addLogRec(String string, State state) {
- mSmHandler.mLogRecords.add(null, string, state, null);
+ protected void addLogRec(String string) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+ smh.mLogRecords.add(this, smh.getCurrentMessage(), string, smh.getCurrentState(),
+ smh.mStateStack[smh.mStateStackTopIndex].state, smh.mDestState);
}
/**
@@ -1412,168 +1501,353 @@ public class StateMachine {
}
/**
- * @return Handler
+ * @return Handler, maybe null if state machine has quit.
*/
public final Handler getHandler() {
return mSmHandler;
}
/**
- * Get a message and set Message.target = this.
+ * Get a message and set Message.target state machine handler.
+ *
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
*
- * @return message or null if SM has quit
+ * @return A Message object from the global pool
*/
- public final Message obtainMessage()
- {
- if (mSmHandler == null) return null;
-
+ public final Message obtainMessage() {
return Message.obtain(mSmHandler);
}
/**
- * Get a message and set Message.target = this and what
+ * Get a message and set Message.target state machine handler, what.
+ *
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
*
* @param what is the assigned to Message.what.
- * @return message or null if SM has quit
+ * @return A Message object from the global pool
*/
public final Message obtainMessage(int what) {
- if (mSmHandler == null) return null;
-
return Message.obtain(mSmHandler, what);
}
/**
- * Get a message and set Message.target = this,
+ * Get a message and set Message.target state machine handler,
* what and obj.
*
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
* @param what is the assigned to Message.what.
* @param obj is assigned to Message.obj.
- * @return message or null if SM has quit
+ * @return A Message object from the global pool
*/
- public final Message obtainMessage(int what, Object obj)
- {
- if (mSmHandler == null) return null;
-
+ public final Message obtainMessage(int what, Object obj) {
return Message.obtain(mSmHandler, what, obj);
}
/**
- * Get a message and set Message.target = this,
+ * Get a message and set Message.target state machine handler,
* what, arg1 and arg2
*
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
* @param what is assigned to Message.what
* @param arg1 is assigned to Message.arg1
- * @param arg2 is assigned to Message.arg2
- * @return A Message object from the global pool or null if
- * SM has quit
+ * @return A Message object from the global pool
*/
- public final Message obtainMessage(int what, int arg1, int arg2)
- {
- if (mSmHandler == null) return null;
+ public final Message obtainMessage(int what, int arg1) {
+ // use this obtain so we don't match the obtain(h, what, Object) method
+ return Message.obtain(mSmHandler, what, arg1, 0);
+ }
+ /**
+ * Get a message and set Message.target state machine handler,
+ * what, arg1 and arg2
+ *
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
+ * @param what is assigned to Message.what
+ * @param arg1 is assigned to Message.arg1
+ * @param arg2 is assigned to Message.arg2
+ * @return A Message object from the global pool
+ */
+ public final Message obtainMessage(int what, int arg1, int arg2) {
return Message.obtain(mSmHandler, what, arg1, arg2);
}
/**
- * Get a message and set Message.target = this,
+ * Get a message and set Message.target state machine handler,
* what, arg1, arg2 and obj
*
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
* @param what is assigned to Message.what
* @param arg1 is assigned to Message.arg1
* @param arg2 is assigned to Message.arg2
* @param obj is assigned to Message.obj
- * @return A Message object from the global pool or null if
- * SM has quit
+ * @return A Message object from the global pool
*/
- public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
- {
- if (mSmHandler == null) return null;
-
+ public final Message obtainMessage(int what, int arg1, int arg2, Object obj) {
return Message.obtain(mSmHandler, what, arg1, arg2, obj);
}
/**
* Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
*/
public final void sendMessage(int what) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.sendMessage(obtainMessage(what));
+ smh.sendMessage(obtainMessage(what));
}
/**
* Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
*/
public final void sendMessage(int what, Object obj) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.sendMessage(obtainMessage(what,obj));
+ smh.sendMessage(obtainMessage(what, obj));
}
/**
* Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public final void sendMessage(int what, int arg1) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessage(obtainMessage(what, arg1));
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public final void sendMessage(int what, int arg1, int arg2) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessage(obtainMessage(what, arg1, arg2));
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public final void sendMessage(int what, int arg1, int arg2, Object obj) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessage(obtainMessage(what, arg1, arg2, obj));
+ }
+
+ /**
+ * Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
*/
public final void sendMessage(Message msg) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.sendMessage(msg);
+ smh.sendMessage(msg);
}
/**
* Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
*/
public final void sendMessageDelayed(int what, long delayMillis) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.sendMessageDelayed(obtainMessage(what), delayMillis);
+ smh.sendMessageDelayed(obtainMessage(what), delayMillis);
}
/**
* Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
*/
public final void sendMessageDelayed(int what, Object obj, long delayMillis) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public final void sendMessageDelayed(int what, int arg1, long delayMillis) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
+ smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis);
}
/**
* Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public final void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageDelayed(obtainMessage(what, arg1, arg2), delayMillis);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ public final void sendMessageDelayed(int what, int arg1, int arg2, Object obj,
+ long delayMillis) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageDelayed(obtainMessage(what, arg1, arg2, obj), delayMillis);
+ }
+
+ /**
+ * Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
*/
public final void sendMessageDelayed(Message msg, long delayMillis) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageDelayed(msg, delayMillis);
+ }
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ protected final void sendMessageAtFrontOfQueue(int what) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.sendMessageDelayed(msg, delayMillis);
+ smh.sendMessageAtFrontOfQueue(obtainMessage(what));
}
/**
* Enqueue a message to the front of the queue for this state machine.
* Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
*/
protected final void sendMessageAtFrontOfQueue(int what, Object obj) {
- mSmHandler.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
}
/**
* Enqueue a message to the front of the queue for this state machine.
* Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
*/
- protected final void sendMessageAtFrontOfQueue(int what) {
- mSmHandler.sendMessageAtFrontOfQueue(obtainMessage(what));
+ protected final void sendMessageAtFrontOfQueue(int what, int arg1) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1));
+ }
+
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2));
}
/**
* Enqueue a message to the front of the queue for this state machine.
* Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
+ */
+ protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2, Object obj) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2, obj));
+ }
+
+ /**
+ * Enqueue a message to the front of the queue for this state machine.
+ * Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
*/
protected final void sendMessageAtFrontOfQueue(Message msg) {
- mSmHandler.sendMessageAtFrontOfQueue(msg);
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(msg);
}
/**
@@ -1581,7 +1855,23 @@ public class StateMachine {
* Protected, may only be called by instances of StateMachine.
*/
protected final void removeMessages(int what) {
- mSmHandler.removeMessages(what);
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.removeMessages(what);
+ }
+
+ /**
+ * Validate that the message was sent by
+ * {@link StateMachine#quit} or {@link StateMachine#quitNow}.
+ * */
+ protected final boolean isQuit(Message msg) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return msg.what == SM_QUIT_CMD;
+
+ return smh.isQuit(msg);
}
/**
@@ -1589,9 +1879,10 @@ public class StateMachine {
*/
protected final void quit() {
// mSmHandler can be null if the state machine is already stopped.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.quit();
+ smh.quit();
}
/**
@@ -1599,9 +1890,10 @@ public class StateMachine {
*/
protected final void quitNow() {
// mSmHandler can be null if the state machine is already stopped.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.quitNow();
+ smh.quitNow();
}
/**
@@ -1609,9 +1901,10 @@ public class StateMachine {
*/
public boolean isDbg() {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return false;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return false;
- return mSmHandler.isDbg();
+ return smh.isDbg();
}
/**
@@ -1621,9 +1914,10 @@ public class StateMachine {
*/
public void setDbg(boolean dbg) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.setDbg(dbg);
+ smh.setDbg(dbg);
}
/**
@@ -1631,10 +1925,11 @@ public class StateMachine {
*/
public void start() {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
/** Send the complete construction message */
- mSmHandler.completeConstruction();
+ smh.completeConstruction();
}
/**
@@ -1647,10 +1942,84 @@ public class StateMachine {
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(getName() + ":");
pw.println(" total records=" + getLogRecCount());
- for (int i=0; i < getLogRecSize(); i++) {
- pw.printf(" rec[%d]: %s\n", i, getLogRec(i).toString(this));
+ for (int i = 0; i < getLogRecSize(); i++) {
+ pw.printf(" rec[%d]: %s\n", i, getLogRec(i).toString());
pw.flush();
}
pw.println("curState=" + getCurrentState().getName());
}
+
+ /**
+ * Log with debug and add to the LogRecords.
+ *
+ * @param s is string log
+ */
+ protected void logAndAddLogRec(String s) {
+ addLogRec(s);
+ log(s);
+ }
+
+ /**
+ * Log with debug
+ *
+ * @param s is string log
+ */
+ protected void log(String s) {
+ Log.d(mName, s);
+ }
+
+ /**
+ * Log with debug attribute
+ *
+ * @param s is string log
+ */
+ protected void logd(String s) {
+ Log.d(mName, s);
+ }
+
+ /**
+ * Log with verbose attribute
+ *
+ * @param s is string log
+ */
+ protected void logv(String s) {
+ Log.v(mName, s);
+ }
+
+ /**
+ * Log with info attribute
+ *
+ * @param s is string log
+ */
+ protected void logi(String s) {
+ Log.i(mName, s);
+ }
+
+ /**
+ * Log with warning attribute
+ *
+ * @param s is string log
+ */
+ protected void logw(String s) {
+ Log.w(mName, s);
+ }
+
+ /**
+ * Log with error attribute
+ *
+ * @param s is string log
+ */
+ protected void loge(String s) {
+ Log.e(mName, s);
+ }
+
+ /**
+ * Log with error attribute
+ *
+ * @param s is string log
+ * @param e is a Throwable which logs additional information.
+ */
+ protected void loge(String s, Throwable e) {
+ Log.e(mName, s, e);
+ }
}
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index 6a09fe0..fa35308 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -16,6 +16,7 @@
package com.android.internal.util;
+import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -24,6 +25,7 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -32,11 +34,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-import android.util.Xml;
-
/** {@hide} */
-public class XmlUtils
-{
+public class XmlUtils {
public static void skipCurrentTag(XmlPullParser parser)
throws XmlPullParserException, IOException {
@@ -123,18 +122,15 @@ public class XmlUtils
return Integer.parseInt(nm.substring(index), base) * sign;
}
- public static final int
- convertValueToUnsignedInt(String value, int defaultValue)
- {
- if (null == value)
+ public static int convertValueToUnsignedInt(String value, int defaultValue) {
+ if (null == value) {
return defaultValue;
+ }
return parseUnsignedIntAttribute(value);
}
- public static final int
- parseUnsignedIntAttribute(CharSequence charSeq)
- {
+ public static int parseUnsignedIntAttribute(CharSequence charSeq) {
String value = charSeq.toString();
long bits;
@@ -903,4 +899,42 @@ public class XmlUtils
}
}
}
+
+ public static int readIntAttribute(XmlPullParser in, String name) throws IOException {
+ final String value = in.getAttributeValue(null, name);
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ throw new ProtocolException("problem parsing " + name + "=" + value + " as int");
+ }
+ }
+
+ public static void writeIntAttribute(XmlSerializer out, String name, int value)
+ throws IOException {
+ out.attribute(null, name, Integer.toString(value));
+ }
+
+ public static long readLongAttribute(XmlPullParser in, String name) throws IOException {
+ final String value = in.getAttributeValue(null, name);
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ throw new ProtocolException("problem parsing " + name + "=" + value + " as long");
+ }
+ }
+
+ public static void writeLongAttribute(XmlSerializer out, String name, long value)
+ throws IOException {
+ out.attribute(null, name, Long.toString(value));
+ }
+
+ public static boolean readBooleanAttribute(XmlPullParser in, String name) {
+ final String value = in.getAttributeValue(null, name);
+ return Boolean.parseBoolean(value);
+ }
+
+ public static void writeBooleanAttribute(XmlSerializer out, String name, boolean value)
+ throws IOException {
+ out.attribute(null, name, Boolean.toString(value));
+ }
}
diff --git a/core/java/com/android/internal/view/ActionBarPolicy.java b/core/java/com/android/internal/view/ActionBarPolicy.java
index 0c6b780..cf69a9d 100644
--- a/core/java/com/android/internal/view/ActionBarPolicy.java
+++ b/core/java/com/android/internal/view/ActionBarPolicy.java
@@ -19,6 +19,7 @@ package com.android.internal.view;
import com.android.internal.R;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
@@ -44,7 +45,10 @@ public class ActionBarPolicy {
}
public boolean showsOverflowMenuButton() {
- return !ViewConfiguration.get(mContext).hasPermanentMenuKey();
+ return !ViewConfiguration.get(mContext).hasPermanentMenuKey() ||
+ ((mContext.getResources().getConfiguration().uiMode &
+ Configuration.UI_MODE_TYPE_TELEVISION) ==
+ Configuration.UI_MODE_TYPE_TELEVISION);
}
public int getEmbeddedMenuWidthLimit() {
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index b76e89d..02bd4ac 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -34,7 +34,7 @@ public class BaseIWindow extends IWindow.Stub {
}
@Override
- public void resized(Rect frame, Rect contentInsets,
+ public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
if (reportDraw) {
try {
diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl
index c7fcab8..77456da 100644
--- a/core/java/com/android/internal/view/IInputMethod.aidl
+++ b/core/java/com/android/internal/view/IInputMethod.aidl
@@ -16,17 +16,15 @@
package com.android.internal.view;
-import android.graphics.Rect;
import android.os.IBinder;
import android.os.ResultReceiver;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
+import android.view.InputChannel;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.view.IInputContext;
-import com.android.internal.view.IInputMethodCallback;
import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.IInputSessionCallback;
/**
* Top-level interface to an input method component (implemented in a
@@ -35,23 +33,23 @@ import com.android.internal.view.IInputMethodSession;
*/
oneway interface IInputMethod {
void attachToken(IBinder token);
-
+
void bindInput(in InputBinding binding);
-
+
void unbindInput();
void startInput(in IInputContext inputContext, in EditorInfo attribute);
void restartInput(in IInputContext inputContext, in EditorInfo attribute);
- void createSession(IInputMethodCallback callback);
-
+ void createSession(in InputChannel channel, IInputSessionCallback callback);
+
void setSessionEnabled(IInputMethodSession session, boolean enabled);
-
+
void revokeSession(IInputMethodSession session);
-
+
void showSoftInput(int flags, in ResultReceiver resultReceiver);
-
+
void hideSoftInput(int flags, in ResultReceiver resultReceiver);
void changeInputMethodSubtype(in InputMethodSubtype subtype);
diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl
index cdec254..90210ce 100644
--- a/core/java/com/android/internal/view/IInputMethodSession.aidl
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -22,7 +22,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.ExtractedText;
-import com.android.internal.view.IInputMethodCallback;
/**
* Sub-interface of IInputMethod which is safe to give to client applications.
@@ -40,14 +39,8 @@ oneway interface IInputMethodSession {
void viewClicked(boolean focusChanged);
void updateCursor(in Rect newCursor);
-
- void displayCompletions(in CompletionInfo[] completions);
-
- void dispatchKeyEvent(int seq, in KeyEvent event, IInputMethodCallback callback);
- void dispatchTrackballEvent(int seq, in MotionEvent event, IInputMethodCallback callback);
-
- void dispatchGenericMotionEvent(int seq, in MotionEvent event, IInputMethodCallback callback);
+ void displayCompletions(in CompletionInfo[] completions);
void appPrivateCommand(String action, in Bundle data);
diff --git a/core/java/com/android/internal/view/IInputMethodCallback.aidl b/core/java/com/android/internal/view/IInputSessionCallback.aidl
index 480cc0e..2b48f33 100644
--- a/core/java/com/android/internal/view/IInputMethodCallback.aidl
+++ b/core/java/com/android/internal/view/IInputSessionCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,19 +16,14 @@
package com.android.internal.view;
-import android.graphics.Rect;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethodSession;
-import android.os.IBinder;
/**
- * Helper interface for IInputMethod to allow the input method to call back
- * to its client with results from incoming calls.
+ * Helper interface for IInputMethod to allow the input method to notify the client when a new
+ * session has been created.
* {@hide}
*/
-oneway interface IInputMethodCallback {
- void finishedEvent(int seq, boolean handled);
+
+oneway interface IInputSessionCallback {
void sessionCreated(IInputMethodSession session);
}
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
index 658f098..14afe21 100644
--- a/core/java/com/android/internal/view/InputBindResult.java
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -18,6 +18,7 @@ package com.android.internal.view;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.InputChannel;
/**
* Bundle of information returned by input method manager about a successful
@@ -30,7 +31,12 @@ public final class InputBindResult implements Parcelable {
* The input method service.
*/
public final IInputMethodSession method;
-
+
+ /**
+ * The input channel used to send input events to this IME.
+ */
+ public final InputChannel channel;
+
/**
* The ID for this input method, as found in InputMethodInfo; null if
* no input method will be bound.
@@ -42,18 +48,25 @@ public final class InputBindResult implements Parcelable {
*/
public final int sequence;
- public InputBindResult(IInputMethodSession _method, String _id, int _sequence) {
+ public InputBindResult(IInputMethodSession _method, InputChannel _channel,
+ String _id, int _sequence) {
method = _method;
+ channel = _channel;
id = _id;
sequence = _sequence;
}
InputBindResult(Parcel source) {
method = IInputMethodSession.Stub.asInterface(source.readStrongBinder());
+ if (source.readInt() != 0) {
+ channel = InputChannel.CREATOR.createFromParcel(source);
+ } else {
+ channel = null;
+ }
id = source.readString();
sequence = source.readInt();
}
-
+
@Override
public String toString() {
return "InputBindResult{" + method + " " + id
@@ -62,12 +75,19 @@ public final class InputBindResult 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.
*/
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStrongInterface(method);
+ if (channel != null) {
+ dest.writeInt(1);
+ channel.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
dest.writeString(id);
dest.writeInt(sequence);
}
@@ -75,17 +95,21 @@ public final class InputBindResult implements Parcelable {
/**
* Used to make this class parcelable.
*/
- public static final Parcelable.Creator<InputBindResult> CREATOR = new Parcelable.Creator<InputBindResult>() {
+ public static final Parcelable.Creator<InputBindResult> CREATOR =
+ new Parcelable.Creator<InputBindResult>() {
+ @Override
public InputBindResult createFromParcel(Parcel source) {
return new InputBindResult(source);
}
+ @Override
public InputBindResult[] newArray(int size) {
return new InputBindResult[size];
}
};
+ @Override
public int describeContents() {
- return 0;
+ return channel != null ? channel.describeContents() : 0;
}
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index 2685046..7ca6c1b 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -107,7 +107,7 @@ public class ActionMenuItem implements MenuItem {
}
public CharSequence getTitleCondensed() {
- return mTitleCondensed;
+ return mTitleCondensed != null ? mTitleCondensed : mTitle;
}
public boolean hasSubMenu() {
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 7189610..39078ca 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -598,11 +598,13 @@ public final class MenuItemImpl implements MenuItem {
mActionView = null;
mActionProvider = actionProvider;
mMenu.onItemsChanged(true); // Measurement can be changed
- mActionProvider.setVisibilityListener(new ActionProvider.VisibilityListener() {
- @Override public void onActionProviderVisibilityChanged(boolean isVisible) {
- mMenu.onItemVisibleChanged(MenuItemImpl.this);
- }
- });
+ if (mActionProvider != null) {
+ mActionProvider.setVisibilityListener(new ActionProvider.VisibilityListener() {
+ @Override public void onActionProviderVisibilityChanged(boolean isVisible) {
+ mMenu.onItemVisibleChanged(MenuItemImpl.this);
+ }
+ });
+ }
return this;
}
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index 0cfe4fd..6120a09 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -84,6 +84,10 @@ public class ActionBarContainer extends FrameLayout {
mBackground = bg;
if (bg != null) {
bg.setCallback(this);
+ if (mActionBarView != null) {
+ mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
+ mActionBarView.getRight(), mActionBarView.getBottom());
+ }
}
setWillNotDraw(mIsSplit ? mSplitBackground == null :
mBackground == null && mStackedBackground == null);
@@ -98,6 +102,10 @@ public class ActionBarContainer extends FrameLayout {
mStackedBackground = bg;
if (bg != null) {
bg.setCallback(this);
+ if ((mIsStacked && mStackedBackground != null)) {
+ mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(),
+ mTabContainer.getRight(), mTabContainer.getBottom());
+ }
}
setWillNotDraw(mIsSplit ? mSplitBackground == null :
mBackground == null && mStackedBackground == null);
@@ -112,6 +120,9 @@ public class ActionBarContainer extends FrameLayout {
mSplitBackground = bg;
if (bg != null) {
bg.setCallback(this);
+ if (mIsSplit && mSplitBackground != null) {
+ mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ }
}
setWillNotDraw(mIsSplit ? mSplitBackground == null :
mBackground == null && mStackedBackground == null);
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index a129496..f359146 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -16,32 +16,42 @@
package com.android.internal.widget;
+import android.view.ViewGroup;
import com.android.internal.app.ActionBarImpl;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
-import android.widget.FrameLayout;
/**
* Special layout for the containing of an overlay action bar (and its
* content) to correctly handle fitting system windows when the content
* has request that its layout ignore them.
*/
-public class ActionBarOverlayLayout extends FrameLayout {
+public class ActionBarOverlayLayout extends ViewGroup {
private int mActionBarHeight;
private ActionBarImpl mActionBar;
private int mWindowVisibility = View.VISIBLE;
+
+ // The main UI elements that we handle the layout of.
private View mContent;
private View mActionBarTop;
+ private View mActionBarBottom;
+
+ // Some interior UI elements.
private ActionBarContainer mContainerView;
private ActionBarView mActionView;
- private View mActionBarBottom;
+
+ private boolean mOverlayMode;
private int mLastSystemUiVisibility;
- private final Rect mZeroRect = new Rect(0, 0, 0, 0);
+ private final Rect mBaseContentInsets = new Rect();
+ private final Rect mLastBaseContentInsets = new Rect();
+ private final Rect mContentInsets = new Rect();
+ private final Rect mBaseInnerInsets = new Rect();
+ private final Rect mInnerInsets = new Rect();
+ private final Rect mLastInnerInsets = new Rect();
static final int[] mActionBarSizeAttr = new int [] {
com.android.internal.R.attr.actionBarSize
@@ -63,8 +73,9 @@ public class ActionBarOverlayLayout extends FrameLayout {
ta.recycle();
}
- public void setActionBar(ActionBarImpl impl) {
+ public void setActionBar(ActionBarImpl impl, boolean overlayMode) {
mActionBar = impl;
+ mOverlayMode = overlayMode;
if (getWindowToken() != null) {
// This is being initialized after being added to a window;
// make sure to update all state now.
@@ -105,8 +116,13 @@ public class ActionBarOverlayLayout extends FrameLayout {
mLastSystemUiVisibility = visible;
final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0;
final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true;
+ final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
if (mActionBar != null) {
- if (barVisible) mActionBar.showForSystem();
+ // We want the bar to be visible if it is not being hidden,
+ // or the app has not turned on a stable UI mode (meaning they
+ // are performing explicit layout around the action bar).
+ mActionBar.enableContentAnimations(!stable);
+ if (barVisible || !stable) mActionBar.showForSystem();
else mActionBar.hideForSystem();
}
if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
@@ -128,7 +144,7 @@ public class ActionBarOverlayLayout extends FrameLayout {
private boolean applyInsets(View view, Rect insets, boolean left, boolean top,
boolean bottom, boolean right) {
boolean changed = false;
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams)view.getLayoutParams();
+ LayoutParams lp = (LayoutParams)view.getLayoutParams();
if (left && lp.leftMargin != insets.left) {
changed = true;
lp.leftMargin = insets.left;
@@ -161,45 +177,183 @@ public class ActionBarOverlayLayout extends FrameLayout {
changed |= applyInsets(mActionBarBottom, insets, true, false, true, true);
}
- // If the window has not requested system UI layout flags, we need to
- // make sure its content is not being covered by system UI... though it
- // will still be covered by the action bar since they have requested it to
- // overlay.
- if ((vis & SYSTEM_UI_LAYOUT_FLAGS) == 0) {
- changed |= applyInsets(mContent, insets, true, true, true, true);
- // The insets are now consumed.
- insets.set(0, 0, 0, 0);
- } else {
- changed |= applyInsets(mContent, mZeroRect, true, true, true, true);
+ mBaseInnerInsets.set(insets);
+ computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
+ if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
+ changed = true;
+ mLastBaseContentInsets.set(mBaseContentInsets);
+ }
+
+ if (changed) {
+ requestLayout();
}
+ // We don't do any more at this point. To correctly compute the content/inner
+ // insets in all cases, we need to know the measured size of the various action
+ // bar elements. fitSystemWindows() happens before the measure pass, so we can't
+ // do that here. Instead we will take this up in onMeasure().
+ return true;
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new LayoutParams(p);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ pullChildren();
+
+ int maxHeight = 0;
+ int maxWidth = 0;
+ int childState = 0;
+
+ int topInset = 0;
+ int bottomInset = 0;
- if (stable || mActionBarTop.getVisibility() == VISIBLE) {
- // The action bar creates additional insets for its content to use.
- insets.top += mActionBarHeight;
+ measureChildWithMargins(mActionBarTop, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ LayoutParams lp = (LayoutParams) mActionBarTop.getLayoutParams();
+ maxWidth = Math.max(maxWidth,
+ mActionBarTop.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+ maxHeight = Math.max(maxHeight,
+ mActionBarTop.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+ childState = combineMeasuredStates(childState, mActionBarTop.getMeasuredState());
+
+ // xlarge screen layout doesn't have bottom action bar.
+ if (mActionBarBottom != null) {
+ measureChildWithMargins(mActionBarBottom, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ lp = (LayoutParams) mActionBarBottom.getLayoutParams();
+ maxWidth = Math.max(maxWidth,
+ mActionBarBottom.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+ maxHeight = Math.max(maxHeight,
+ mActionBarBottom.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+ childState = combineMeasuredStates(childState, mActionBarBottom.getMeasuredState());
}
- if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
- View tabs = mContainerView.getTabContainer();
- if (stable || (tabs != null && tabs.getVisibility() == VISIBLE)) {
- // If tabs are not embedded, adjust insets to account for them.
- insets.top += mActionBarHeight;
+ final int vis = getWindowSystemUiVisibility();
+ final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
+
+ if (stable) {
+ // This is the standard space needed for the action bar. For stable measurement,
+ // we can't depend on the size currently reported by it -- this must remain constant.
+ topInset = mActionBarHeight;
+ if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
+ View tabs = mContainerView.getTabContainer();
+ if (tabs != null) {
+ // If tabs are not embedded, increase space on top to account for them.
+ topInset += mActionBarHeight;
+ }
}
+ } else if (mActionBarTop.getVisibility() == VISIBLE) {
+ // This is the space needed on top of the window for all of the action bar
+ // and tabs.
+ topInset = mActionBarTop.getMeasuredHeight();
}
if (mActionView.isSplitActionBar()) {
- if (stable || (mActionBarBottom != null
- && mActionBarBottom.getVisibility() == VISIBLE)) {
- // If action bar is split, adjust buttom insets for it.
- insets.bottom += mActionBarHeight;
+ // If action bar is split, adjust bottom insets for it.
+ if (mActionBarBottom != null) {
+ if (stable) {
+ bottomInset = mActionBarHeight;
+ } else {
+ bottomInset = mActionBarBottom.getMeasuredHeight();
+ }
}
}
- if (changed) {
- requestLayout();
+ // If the window has not requested system UI layout flags, we need to
+ // make sure its content is not being covered by system UI... though it
+ // will still be covered by the action bar if they have requested it to
+ // overlay.
+ mContentInsets.set(mBaseContentInsets);
+ mInnerInsets.set(mBaseInnerInsets);
+ if (!mOverlayMode && !stable) {
+ mContentInsets.top += topInset;
+ mContentInsets.bottom += bottomInset;
+ } else {
+ mInnerInsets.top += topInset;
+ mInnerInsets.bottom += bottomInset;
+ }
+ applyInsets(mContent, mContentInsets, true, true, true, true);
+
+ if (!mLastInnerInsets.equals(mInnerInsets)) {
+ // If the inner insets have changed, we need to dispatch this down to
+ // the app's fitSystemWindows(). We do this before measuring the content
+ // view to keep the same semantics as the normal fitSystemWindows() call.
+ mLastInnerInsets.set(mInnerInsets);
+ super.fitSystemWindows(mInnerInsets);
+ }
+
+ measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ lp = (LayoutParams) mContent.getLayoutParams();
+ maxWidth = Math.max(maxWidth,
+ mContent.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
+ maxHeight = Math.max(maxHeight,
+ mContent.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
+ childState = combineMeasuredStates(childState, mContent.getMeasuredState());
+
+ // Account for padding too
+ maxWidth += getPaddingLeft() + getPaddingRight();
+ maxHeight += getPaddingTop() + getPaddingBottom();
+
+ // Check against our minimum height and width
+ maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
+ maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
+
+ setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
+ resolveSizeAndState(maxHeight, heightMeasureSpec,
+ childState << MEASURED_HEIGHT_STATE_SHIFT));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ final int count = getChildCount();
+
+ final int parentLeft = getPaddingLeft();
+ final int parentRight = right - left - getPaddingRight();
+
+ final int parentTop = getPaddingTop();
+ final int parentBottom = bottom - top - getPaddingBottom();
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+
+ final int width = child.getMeasuredWidth();
+ final int height = child.getMeasuredHeight();
+
+ int childLeft = parentLeft + lp.leftMargin;
+ int childTop;
+ if (child == mActionBarBottom) {
+ childTop = parentBottom - height - lp.bottomMargin;
+ } else {
+ childTop = parentTop + lp.topMargin;
+ }
+
+ child.layout(childLeft, childTop, childLeft + width, childTop + height);
+ }
}
+ }
- return super.fitSystemWindows(insets);
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ return false;
}
void pullChildren() {
@@ -212,4 +366,23 @@ public class ActionBarOverlayLayout extends FrameLayout {
mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar);
}
}
+
+
+ public static class LayoutParams extends MarginLayoutParams {
+ public LayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+
+ public LayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public LayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+
+ public LayoutParams(ViewGroup.MarginLayoutParams source) {
+ super(source);
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 534e034..b9b6ee5 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -36,7 +36,6 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.content.res.TypedArray;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
@@ -87,13 +86,15 @@ public class ActionBarView extends AbsActionBarView {
ActionBar.DISPLAY_TITLE_MULTIPLE_LINES;
private static final int DEFAULT_CUSTOM_GRAVITY = Gravity.START | Gravity.CENTER_VERTICAL;
-
+
private int mNavigationMode;
private int mDisplayOptions = -1;
private CharSequence mTitle;
private CharSequence mSubtitle;
private Drawable mIcon;
private Drawable mLogo;
+ private CharSequence mHomeDescription;
+ private int mHomeDescriptionRes;
private HomeView mHomeLayout;
private HomeView mExpandedHomeLayout;
@@ -112,7 +113,7 @@ public class ActionBarView extends AbsActionBarView {
private int mProgressBarPadding;
private int mItemPadding;
-
+
private int mTitleStyleRes;
private int mSubtitleStyleRes;
private int mProgressStyle;
@@ -125,7 +126,8 @@ public class ActionBarView extends AbsActionBarView {
private boolean mWasHomeEnabled; // Was it enabled before action view expansion?
private MenuBuilder mOptionsMenu;
-
+ private boolean mMenuPrepared;
+
private ActionBarContextView mContextView;
private ActionMenuItem mLogoNavItem;
@@ -164,7 +166,10 @@ public class ActionBarView extends AbsActionBarView {
private final OnClickListener mUpClickListener = new OnClickListener() {
public void onClick(View v) {
- mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem);
+ if (mMenuPrepared) {
+ // Only invoke the window callback if the options menu has been initialized.
+ mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mLogoNavItem);
+ }
}
};
@@ -183,7 +188,7 @@ public class ActionBarView extends AbsActionBarView {
ActionBar.NAVIGATION_MODE_STANDARD);
mTitle = a.getText(R.styleable.ActionBar_title);
mSubtitle = a.getText(R.styleable.ActionBar_subtitle);
-
+
mLogo = a.getDrawable(R.styleable.ActionBar_logo);
if (mLogo == null) {
if (context instanceof Activity) {
@@ -227,7 +232,7 @@ public class ActionBarView extends AbsActionBarView {
mExpandedHomeLayout.setOnClickListener(mExpandedActionViewUpListener);
mExpandedHomeLayout.setContentDescription(getResources().getText(
R.string.action_bar_up_description));
-
+
// This needs to highlight/be focusable on its own.
// TODO: Clean up the handoff between expanded/normal.
final Drawable upBackground = mUpGoerFive.getBackground();
@@ -256,9 +261,9 @@ public class ActionBarView extends AbsActionBarView {
}
mContentHeight = a.getLayoutDimension(R.styleable.ActionBar_height, 0);
-
+
a.recycle();
-
+
mLogoNavItem = new ActionMenuItem(context, 0, android.R.id.home, 0, 0, mTitle);
mUpGoerFive.setOnClickListener(mUpClickListener);
@@ -285,6 +290,10 @@ public class ActionBarView extends AbsActionBarView {
initTitle();
}
+ if (mHomeDescriptionRes != 0) {
+ setHomeActionContentDescription(mHomeDescriptionRes);
+ }
+
if (mTabScrollView != null && mIncludeTabs) {
ViewGroup.LayoutParams lp = mTabScrollView.getLayoutParams();
if (lp != null) {
@@ -402,6 +411,10 @@ public class ActionBarView extends AbsActionBarView {
mCallback = callback;
}
+ public void setMenuPrepared() {
+ mMenuPrepared = true;
+ }
+
public void setMenu(Menu menu, MenuPresenter.Callback cb) {
if (menu == mOptionsMenu) return;
@@ -540,6 +553,7 @@ public class ActionBarView extends AbsActionBarView {
if (mLogoNavItem != null) {
mLogoNavItem.setTitle(title);
}
+ mUpGoerFive.setContentDescription(buildHomeContentDescription());
}
public CharSequence getSubtitle() {
@@ -556,6 +570,7 @@ public class ActionBarView extends AbsActionBarView {
(!TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mSubtitle));
mTitleLayout.setVisibility(visible ? VISIBLE : GONE);
}
+ mUpGoerFive.setContentDescription(buildHomeContentDescription());
}
public void setHomeButtonEnabled(boolean enable) {
@@ -582,14 +597,43 @@ public class ActionBarView extends AbsActionBarView {
mUpGoerFive.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
} else {
mUpGoerFive.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ mUpGoerFive.setContentDescription(buildHomeContentDescription());
+ }
+ }
+
+ /**
+ * Compose a content description for the Home/Up affordance.
+ *
+ * <p>As this encompasses the icon/logo, title and subtitle all in one, we need
+ * a description for the whole wad of stuff that can be localized properly.</p>
+ */
+ private CharSequence buildHomeContentDescription() {
+ final CharSequence homeDesc;
+ if (mHomeDescription != null) {
+ homeDesc = mHomeDescription;
+ } else {
if ((mDisplayOptions & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
- mUpGoerFive.setContentDescription(mContext.getResources().getText(
- R.string.action_bar_up_description));
+ homeDesc = mContext.getResources().getText(R.string.action_bar_up_description);
} else {
- mUpGoerFive.setContentDescription(mContext.getResources().getText(
- R.string.action_bar_home_description));
+ homeDesc = mContext.getResources().getText(R.string.action_bar_home_description);
}
}
+
+ final CharSequence title = getTitle();
+ final CharSequence subtitle = getSubtitle();
+ if (!TextUtils.isEmpty(title)) {
+ final String result;
+ if (!TextUtils.isEmpty(subtitle)) {
+ result = getResources().getString(
+ R.string.action_bar_home_subtitle_description_format,
+ title, subtitle, homeDesc);
+ } else {
+ result = getResources().getString(R.string.action_bar_home_description_format,
+ title, homeDesc);
+ }
+ return result;
+ }
+ return homeDesc;
}
public void setDisplayOptions(int options) {
@@ -640,7 +684,7 @@ public class ActionBarView extends AbsActionBarView {
removeView(mCustomNavView);
}
}
-
+
if (mTitleLayout != null &&
(flagsChanged & ActionBar.DISPLAY_TITLE_MULTIPLE_LINES) != 0) {
if ((options & ActionBar.DISPLAY_TITLE_MULTIPLE_LINES) != 0) {
@@ -713,12 +757,13 @@ public class ActionBarView extends AbsActionBarView {
removeView(mTabScrollView);
}
}
-
+
switch (mode) {
case ActionBar.NAVIGATION_MODE_LIST:
if (mSpinner == null) {
mSpinner = new Spinner(mContext, null,
com.android.internal.R.attr.actionDropDownStyle);
+ mSpinner.setId(com.android.internal.R.id.action_bar_spinner);
mListNavLayout = new LinearLayout(mContext, null,
com.android.internal.R.attr.actionBarTabBarStyle);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
@@ -765,11 +810,11 @@ public class ActionBarView extends AbsActionBarView {
public View getCustomNavigationView() {
return mCustomNavView;
}
-
+
public int getNavigationMode() {
return mNavigationMode;
}
-
+
public int getDisplayOptions() {
return mDisplayOptions;
}
@@ -834,6 +879,8 @@ public class ActionBarView extends AbsActionBarView {
(TextUtils.isEmpty(mTitle) && TextUtils.isEmpty(mSubtitle))) {
// Don't show while in expanded mode or with empty text
mTitleLayout.setVisibility(GONE);
+ } else {
+ mTitleLayout.setVisibility(VISIBLE);
}
}
@@ -907,7 +954,7 @@ public class ActionBarView extends AbsActionBarView {
throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
"with android:layout_width=\"match_parent\" (or fill_parent)");
}
-
+
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.AT_MOST) {
throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
@@ -918,7 +965,7 @@ public class ActionBarView extends AbsActionBarView {
int maxHeight = mContentHeight >= 0 ?
mContentHeight : MeasureSpec.getSize(heightMeasureSpec);
-
+
final int verticalPadding = getPaddingTop() + getPaddingBottom();
final int paddingLeft = getPaddingLeft();
final int paddingRight = getPaddingRight();
@@ -954,7 +1001,7 @@ public class ActionBarView extends AbsActionBarView {
availableWidth = Math.max(0, availableWidth - homeOffsetWidth);
leftOfCenter = Math.max(0, availableWidth - homeOffsetWidth);
}
-
+
if (mMenuView != null && mMenuView.getParent() == this) {
availableWidth = measureChildView(mMenuView, availableWidth, exactHeightSpec, 0);
rightOfCenter = Math.max(0, rightOfCenter - mMenuView.getMeasuredWidth());
@@ -1295,6 +1342,23 @@ public class ActionBarView extends AbsActionBarView {
}
}
+ public void setHomeAsUpIndicator(Drawable indicator) {
+ mHomeLayout.setUpIndicator(indicator);
+ }
+
+ public void setHomeAsUpIndicator(int resId) {
+ mHomeLayout.setUpIndicator(resId);
+ }
+
+ public void setHomeActionContentDescription(CharSequence description) {
+ mHomeDescription = description;
+ }
+
+ public void setHomeActionContentDescription(int resId) {
+ mHomeDescriptionRes = resId;
+ mHomeDescription = resId != 0 ? getResources().getText(resId) : null;
+ }
+
static class SavedState extends BaseSavedState {
int expandedMenuItemId;
boolean isOverflowOpen;
@@ -1329,9 +1393,11 @@ public class ActionBarView extends AbsActionBarView {
}
private static class HomeView extends FrameLayout {
- private View mUpView;
+ private ImageView mUpView;
private ImageView mIconView;
private int mUpWidth;
+ private int mUpIndicatorRes;
+ private Drawable mDefaultUpIndicator;
private static final long DEFAULT_TRANSITION_DURATION = 150;
@@ -1356,6 +1422,25 @@ public class ActionBarView extends AbsActionBarView {
mIconView.setImageDrawable(icon);
}
+ public void setUpIndicator(Drawable d) {
+ mUpView.setImageDrawable(d != null ? d : mDefaultUpIndicator);
+ mUpIndicatorRes = 0;
+ }
+
+ public void setUpIndicator(int resId) {
+ mUpIndicatorRes = resId;
+ mUpView.setImageDrawable(resId != 0 ? getResources().getDrawable(resId) : null);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (mUpIndicatorRes != 0) {
+ // Reload for config change
+ setUpIndicator(mUpIndicatorRes);
+ }
+ }
+
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
@@ -1379,8 +1464,9 @@ public class ActionBarView extends AbsActionBarView {
@Override
protected void onFinishInflate() {
- mUpView = findViewById(com.android.internal.R.id.up);
+ mUpView = (ImageView) findViewById(com.android.internal.R.id.up);
mIconView = (ImageView) findViewById(com.android.internal.R.id.home);
+ mDefaultUpIndicator = mUpView.getDrawable();
}
public int getStartOffset() {
@@ -1586,15 +1672,10 @@ public class ActionBarView extends AbsActionBarView {
mTitleLayout.setVisibility(VISIBLE);
}
}
- if (mTabScrollView != null && mNavigationMode == ActionBar.NAVIGATION_MODE_TABS) {
- mTabScrollView.setVisibility(VISIBLE);
- }
- if (mSpinner != null && mNavigationMode == ActionBar.NAVIGATION_MODE_LIST) {
- mSpinner.setVisibility(VISIBLE);
- }
- if (mCustomNavView != null && (mDisplayOptions & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
- mCustomNavView.setVisibility(VISIBLE);
- }
+ if (mTabScrollView != null) mTabScrollView.setVisibility(VISIBLE);
+ if (mSpinner != null) mSpinner.setVisibility(VISIBLE);
+ if (mCustomNavView != null) mCustomNavView.setVisibility(VISIBLE);
+
mExpandedHomeLayout.setIcon(null);
mCurrentExpandedItem = null;
setHomeButtonEnabled(mWasHomeEnabled); // Set by expandItemActionView above
diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java
deleted file mode 100644
index af3fd42..0000000
--- a/core/java/com/android/internal/widget/DigitalClock.java
+++ /dev/null
@@ -1,245 +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 com.android.internal.widget;
-
-import com.android.internal.R;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.graphics.Typeface;
-import android.os.Handler;
-import android.provider.Settings;
-import android.text.format.DateFormat;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import java.lang.ref.WeakReference;
-import java.text.DateFormatSymbols;
-import java.util.Calendar;
-
-/**
- * Displays the time
- */
-public class DigitalClock extends RelativeLayout {
-
- private static final String SYSTEM = "/system/fonts/";
- private static final String SYSTEM_FONT_TIME_BACKGROUND = SYSTEM + "AndroidClock.ttf";
- private static final String SYSTEM_FONT_TIME_FOREGROUND = SYSTEM + "AndroidClock_Highlight.ttf";
- private final static String M12 = "h:mm";
- private final static String M24 = "kk:mm";
-
- private Calendar mCalendar;
- private String mFormat;
- private TextView mTimeDisplayBackground;
- private TextView mTimeDisplayForeground;
- private AmPm mAmPm;
- private ContentObserver mFormatChangeObserver;
- private int mAttached = 0; // for debugging - tells us whether attach/detach is unbalanced
-
- /* called by system on minute ticks */
- private final Handler mHandler = new Handler();
- private BroadcastReceiver mIntentReceiver;
-
- private static final Typeface sBackgroundFont;
- private static final Typeface sForegroundFont;
-
- static {
- sBackgroundFont = Typeface.createFromFile(SYSTEM_FONT_TIME_BACKGROUND);
- sForegroundFont = Typeface.createFromFile(SYSTEM_FONT_TIME_FOREGROUND);
- }
-
- private static class TimeChangedReceiver extends BroadcastReceiver {
- private WeakReference<DigitalClock> mClock;
- private Context mContext;
-
- public TimeChangedReceiver(DigitalClock clock) {
- mClock = new WeakReference<DigitalClock>(clock);
- mContext = clock.getContext();
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- // Post a runnable to avoid blocking the broadcast.
- final boolean timezoneChanged =
- intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED);
- final DigitalClock clock = mClock.get();
- if (clock != null) {
- clock.mHandler.post(new Runnable() {
- public void run() {
- if (timezoneChanged) {
- clock.mCalendar = Calendar.getInstance();
- }
- clock.updateTime();
- }
- });
- } else {
- try {
- mContext.unregisterReceiver(this);
- } catch (RuntimeException e) {
- // Shouldn't happen
- }
- }
- }
- };
-
- static class AmPm {
- private TextView mAmPmTextView;
- private String mAmString, mPmString;
-
- AmPm(View parent, Typeface tf) {
- // No longer used, uncomment if we decide to use AM/PM indicator again
- // mAmPmTextView = (TextView) parent.findViewById(R.id.am_pm);
- if (mAmPmTextView != null && tf != null) {
- mAmPmTextView.setTypeface(tf);
- }
-
- String[] ampm = new DateFormatSymbols().getAmPmStrings();
- mAmString = ampm[0];
- mPmString = ampm[1];
- }
-
- void setShowAmPm(boolean show) {
- if (mAmPmTextView != null) {
- mAmPmTextView.setVisibility(show ? View.VISIBLE : View.GONE);
- }
- }
-
- void setIsMorning(boolean isMorning) {
- if (mAmPmTextView != null) {
- mAmPmTextView.setText(isMorning ? mAmString : mPmString);
- }
- }
- }
-
- private static class FormatChangeObserver extends ContentObserver {
- private WeakReference<DigitalClock> mClock;
- private Context mContext;
- public FormatChangeObserver(DigitalClock clock) {
- super(new Handler());
- mClock = new WeakReference<DigitalClock>(clock);
- mContext = clock.getContext();
- }
- @Override
- public void onChange(boolean selfChange) {
- DigitalClock digitalClock = mClock.get();
- if (digitalClock != null) {
- digitalClock.setDateFormat();
- digitalClock.updateTime();
- } else {
- try {
- mContext.getContentResolver().unregisterContentObserver(this);
- } catch (RuntimeException e) {
- // Shouldn't happen
- }
- }
- }
- }
-
- public DigitalClock(Context context) {
- this(context, null);
- }
-
- public DigitalClock(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- /* The time display consists of two tones. That's why we have two overlapping text views. */
- mTimeDisplayBackground = (TextView) findViewById(R.id.timeDisplayBackground);
- mTimeDisplayBackground.setTypeface(sBackgroundFont);
- mTimeDisplayBackground.setVisibility(View.INVISIBLE);
-
- mTimeDisplayForeground = (TextView) findViewById(R.id.timeDisplayForeground);
- mTimeDisplayForeground.setTypeface(sForegroundFont);
- mAmPm = new AmPm(this, null);
- mCalendar = Calendar.getInstance();
-
- setDateFormat();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- mAttached++;
-
- /* monitor time ticks, time changed, timezone */
- if (mIntentReceiver == null) {
- mIntentReceiver = new TimeChangedReceiver(this);
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_TIME_TICK);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- mContext.registerReceiver(mIntentReceiver, filter);
- }
-
- /* monitor 12/24-hour display preference */
- if (mFormatChangeObserver == null) {
- mFormatChangeObserver = new FormatChangeObserver(this);
- mContext.getContentResolver().registerContentObserver(
- Settings.System.CONTENT_URI, true, mFormatChangeObserver);
- }
-
- updateTime();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- mAttached--;
-
- if (mIntentReceiver != null) {
- mContext.unregisterReceiver(mIntentReceiver);
- }
- if (mFormatChangeObserver != null) {
- mContext.getContentResolver().unregisterContentObserver(
- mFormatChangeObserver);
- }
-
- mFormatChangeObserver = null;
- mIntentReceiver = null;
- }
-
- void updateTime(Calendar c) {
- mCalendar = c;
- updateTime();
- }
-
- public void updateTime() {
- mCalendar.setTimeInMillis(System.currentTimeMillis());
-
- CharSequence newTime = DateFormat.format(mFormat, mCalendar);
- mTimeDisplayBackground.setText(newTime);
- mTimeDisplayForeground.setText(newTime);
- mAmPm.setIsMorning(mCalendar.get(Calendar.AM_PM) == 0);
- }
-
- private void setDateFormat() {
- mFormat = android.text.format.DateFormat.is24HourFormat(getContext())
- ? M24 : M12;
- mAmPm.setShowAmPm(mFormat.equals(M12));
- }
-}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 907b52a..d3ead26 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -123,14 +123,14 @@ public class LockPatternUtils {
*/
public static final int ID_DEFAULT_STATUS_WIDGET = -2;
- protected final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
- protected final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
- protected final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
+ public final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
+ public final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
+ public final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
public static final String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate";
- protected final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
- protected final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled";
- protected final static String LOCKSCREEN_OPTIONS = "lockscreen.options";
+ public final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
+ public final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled";
+ public final static String LOCKSCREEN_OPTIONS = "lockscreen.options";
public final static String LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK
= "lockscreen.biometric_weak_fallback";
public final static String BIOMETRIC_WEAK_EVER_CHOSEN_KEY
@@ -138,7 +138,11 @@ public class LockPatternUtils {
public final static String LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS
= "lockscreen.power_button_instantly_locks";
- protected final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory";
+ public final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory";
+
+ private static final String LOCK_SCREEN_OWNER_INFO = Settings.Secure.LOCK_SCREEN_OWNER_INFO;
+ private static final String LOCK_SCREEN_OWNER_INFO_ENABLED =
+ Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
private final Context mContext;
private final ContentResolver mContentResolver;
@@ -526,6 +530,22 @@ public class LockPatternUtils {
}
}
+ public void setOwnerInfo(String info, int userId) {
+ setString(LOCK_SCREEN_OWNER_INFO, info, userId);
+ }
+
+ public void setOwnerInfoEnabled(boolean enabled) {
+ setBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, enabled);
+ }
+
+ public String getOwnerInfo(int userId) {
+ return getString(LOCK_SCREEN_OWNER_INFO);
+ }
+
+ public boolean isOwnerInfoEnabled() {
+ return getBoolean(LOCK_SCREEN_OWNER_INFO_ENABLED, false);
+ }
+
/**
* Compute the password quality from the given password string.
*/
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 7a76ab0..b066d70 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -50,7 +50,6 @@ import java.util.List;
* "correct" states.
*/
public class LockPatternView extends View {
- private static final String TAG = "LockPatternView";
// Aspect to use when rendering this view
private static final int ASPECT_SQUARE = 0; // View will be the minimum of width/height
private static final int ASPECT_LOCK_WIDTH = 1; // Fixed width; height will be minimum of (w,h)
@@ -62,9 +61,6 @@ public class LockPatternView extends View {
private Paint mPaint = new Paint();
private Paint mPathPaint = new Paint();
- // TODO: make this common with PhoneWindow
- static final int STATUS_BAR_HEIGHT = 25;
-
/**
* How many milliseconds we spend animating each circle of a lock pattern
* if the animating mode is set. The entire animation should take this
@@ -72,6 +68,12 @@ public class LockPatternView extends View {
*/
private static final int MILLIS_PER_CIRCLE_ANIMATING = 700;
+ /**
+ * This can be used to avoid updating the display for very small motions or noisy panels.
+ * It didn't seem to have much impact on the devices tested, so currently set to 0.
+ */
+ private static final float DRAG_THRESHHOLD = 0.0f;
+
private OnPatternListener mOnPatternListener;
private ArrayList<Cell> mPattern = new ArrayList<Cell>(9);
@@ -117,6 +119,7 @@ public class LockPatternView extends View {
private final Path mCurrentPath = new Path();
private final Rect mInvalidate = new Rect();
+ private final Rect mTmpInvalidateRect = new Rect();
private int mBitmapWidth;
private int mBitmapHeight;
@@ -125,7 +128,6 @@ public class LockPatternView extends View {
private final Matrix mArrowMatrix = new Matrix();
private final Matrix mCircleMatrix = new Matrix();
-
/**
* Represents a cell in the 3 X 3 matrix of the unlock pattern view.
*/
@@ -673,11 +675,13 @@ public class LockPatternView extends View {
private void handleActionMove(MotionEvent event) {
// Handle all recent motion events so we don't skip any cells even when the device
// is busy...
+ final float radius = (mSquareWidth * mDiameterFactor * 0.5f);
final int historySize = event.getHistorySize();
+ mTmpInvalidateRect.setEmpty();
+ boolean invalidateNow = false;
for (int i = 0; i < historySize + 1; i++) {
final float x = i < historySize ? event.getHistoricalX(i) : event.getX();
final float y = i < historySize ? event.getHistoricalY(i) : event.getY();
- final int patternSizePreHitDetect = mPattern.size();
Cell hitCell = detectAndAddHit(x, y);
final int patternSize = mPattern.size();
if (hitCell != null && patternSize == 1) {
@@ -687,114 +691,49 @@ public class LockPatternView extends View {
// note current x and y for rubber banding of in progress patterns
final float dx = Math.abs(x - mInProgressX);
final float dy = Math.abs(y - mInProgressY);
- if (dx + dy > mSquareWidth * 0.01f) {
- float oldX = mInProgressX;
- float oldY = mInProgressY;
-
- mInProgressX = x;
- mInProgressY = y;
-
- if (mPatternInProgress && patternSize > 0) {
- final ArrayList<Cell> pattern = mPattern;
- final float radius = mSquareWidth * mDiameterFactor * 0.5f;
-
- final Cell lastCell = pattern.get(patternSize - 1);
-
- float startX = getCenterXForColumn(lastCell.column);
- float startY = getCenterYForRow(lastCell.row);
-
- float left;
- float top;
- float right;
- float bottom;
-
- final Rect invalidateRect = mInvalidate;
-
- if (startX < x) {
- left = startX;
- right = x;
- } else {
- left = x;
- right = startX;
- }
-
- if (startY < y) {
- top = startY;
- bottom = y;
- } else {
- top = y;
- bottom = startY;
- }
-
- // Invalidate between the pattern's last cell and the current location
- invalidateRect.set((int) (left - radius), (int) (top - radius),
- (int) (right + radius), (int) (bottom + radius));
-
- if (startX < oldX) {
- left = startX;
- right = oldX;
- } else {
- left = oldX;
- right = startX;
- }
-
- if (startY < oldY) {
- top = startY;
- bottom = oldY;
- } else {
- top = oldY;
- bottom = startY;
- }
-
- // Invalidate between the pattern's last cell and the previous location
- invalidateRect.union((int) (left - radius), (int) (top - radius),
- (int) (right + radius), (int) (bottom + radius));
-
- // Invalidate between the pattern's new cell and the pattern's previous cell
- if (hitCell != null) {
- startX = getCenterXForColumn(hitCell.column);
- startY = getCenterYForRow(hitCell.row);
-
- if (patternSize >= 2) {
- // (re-using hitcell for old cell)
- hitCell = pattern.get(patternSize - 1 - (patternSize - patternSizePreHitDetect));
- oldX = getCenterXForColumn(hitCell.column);
- oldY = getCenterYForRow(hitCell.row);
-
- if (startX < oldX) {
- left = startX;
- right = oldX;
- } else {
- left = oldX;
- right = startX;
- }
-
- if (startY < oldY) {
- top = startY;
- bottom = oldY;
- } else {
- top = oldY;
- bottom = startY;
- }
- } else {
- left = right = startX;
- top = bottom = startY;
- }
-
- final float widthOffset = mSquareWidth / 2f;
- final float heightOffset = mSquareHeight / 2f;
-
- invalidateRect.set((int) (left - widthOffset),
- (int) (top - heightOffset), (int) (right + widthOffset),
- (int) (bottom + heightOffset));
- }
+ if (dx > DRAG_THRESHHOLD || dy > DRAG_THRESHHOLD) {
+ invalidateNow = true;
+ }
- invalidate(invalidateRect);
- } else {
- invalidate();
+ if (mPatternInProgress && patternSize > 0) {
+ final ArrayList<Cell> pattern = mPattern;
+ final Cell lastCell = pattern.get(patternSize - 1);
+ float lastCellCenterX = getCenterXForColumn(lastCell.column);
+ float lastCellCenterY = getCenterYForRow(lastCell.row);
+
+ // Adjust for drawn segment from last cell to (x,y). Radius accounts for line width.
+ float left = Math.min(lastCellCenterX, x) - radius;
+ float right = Math.max(lastCellCenterX, x) + radius;
+ float top = Math.min(lastCellCenterY, y) - radius;
+ float bottom = Math.max(lastCellCenterY, y) + radius;
+
+ // Invalidate between the pattern's new cell and the pattern's previous cell
+ if (hitCell != null) {
+ final float width = mSquareWidth * 0.5f;
+ final float height = mSquareHeight * 0.5f;
+ final float hitCellCenterX = getCenterXForColumn(hitCell.column);
+ final float hitCellCenterY = getCenterYForRow(hitCell.row);
+
+ left = Math.min(hitCellCenterX - width, left);
+ right = Math.max(hitCellCenterX + width, right);
+ top = Math.min(hitCellCenterY - height, top);
+ bottom = Math.max(hitCellCenterY + height, bottom);
}
+
+ // Invalidate between the pattern's last cell and the previous location
+ mTmpInvalidateRect.union(Math.round(left), Math.round(top),
+ Math.round(right), Math.round(bottom));
}
}
+ mInProgressX = event.getX();
+ mInProgressY = event.getY();
+
+ // To save updates, we only invalidate if the user moved beyond a certain amount.
+ if (invalidateNow) {
+ mInvalidate.union(mTmpInvalidateRect);
+ invalidate(mInvalidate);
+ mInvalidate.set(mTmpInvalidateRect);
+ }
}
private void sendAccessEvent(int resId) {
diff --git a/core/java/com/android/internal/widget/LockSettingsService.java b/core/java/com/android/internal/widget/LockSettingsService.java
deleted file mode 100644
index 4ecbd16..0000000
--- a/core/java/com/android/internal/widget/LockSettingsService.java
+++ /dev/null
@@ -1,405 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-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.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.Slog;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.Arrays;
-
-/**
- * Keeps the lock pattern/password data and related settings for each user.
- * Used by LockPatternUtils. Needs to be a service because Settings app also needs
- * to be able to save lockscreen information for secondary users.
- * @hide
- */
-public class LockSettingsService extends ILockSettings.Stub {
-
- private final DatabaseHelper mOpenHelper;
- private static final String TAG = "LockSettingsService";
-
- private static final String TABLE = "locksettings";
- private static final String COLUMN_KEY = "name";
- private static final String COLUMN_USERID = "user";
- private static final String COLUMN_VALUE = "value";
-
- private static final String[] COLUMNS_FOR_QUERY = {
- COLUMN_VALUE
- };
-
- private static final String SYSTEM_DIRECTORY = "/system/";
- private static final String LOCK_PATTERN_FILE = "gesture.key";
- private static final String LOCK_PASSWORD_FILE = "password.key";
-
- private final Context mContext;
-
- public LockSettingsService(Context context) {
- mContext = context;
- // Open the database
- mOpenHelper = new DatabaseHelper(mContext);
- }
-
- public void systemReady() {
- migrateOldData();
- }
-
- private void migrateOldData() {
- try {
- if (getString("migrated", null, 0) != null) {
- // Already migrated
- return;
- }
-
- final ContentResolver cr = mContext.getContentResolver();
- for (String validSetting : VALID_SETTINGS) {
- String value = Settings.Secure.getString(cr, validSetting);
- if (value != null) {
- setString(validSetting, value, 0);
- }
- }
- // No need to move the password / pattern files. They're already in the right place.
- setString("migrated", "true", 0);
- Slog.i(TAG, "Migrated lock settings to new location");
- } catch (RemoteException re) {
- Slog.e(TAG, "Unable to migrate old data");
- }
- }
-
- private static final void checkWritePermission(int userId) {
- final int callingUid = Binder.getCallingUid();
- if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
- throw new SecurityException("uid=" + callingUid
- + " not authorized to write lock settings");
- }
- }
-
- private static final void checkPasswordReadPermission(int userId) {
- final int callingUid = Binder.getCallingUid();
- if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
- throw new SecurityException("uid=" + callingUid
- + " not authorized to read lock password");
- }
- }
-
- private static final void checkReadPermission(int userId) {
- final int callingUid = Binder.getCallingUid();
- 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);
- }
- }
-
- @Override
- public void setBoolean(String key, boolean value, int userId) throws RemoteException {
- checkWritePermission(userId);
-
- writeToDb(key, value ? "1" : "0", userId);
- }
-
- @Override
- public void setLong(String key, long value, int userId) throws RemoteException {
- checkWritePermission(userId);
-
- writeToDb(key, Long.toString(value), userId);
- }
-
- @Override
- public void setString(String key, String value, int userId) throws RemoteException {
- checkWritePermission(userId);
-
- writeToDb(key, value, userId);
- }
-
- @Override
- public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
- //checkReadPermission(userId);
-
- String value = readFromDb(key, null, userId);
- return TextUtils.isEmpty(value) ?
- defaultValue : (value.equals("1") || value.equals("true"));
- }
-
- @Override
- public long getLong(String key, long defaultValue, int userId) throws RemoteException {
- //checkReadPermission(userId);
-
- String value = readFromDb(key, null, userId);
- return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
- }
-
- @Override
- public String getString(String key, String defaultValue, int userId) throws RemoteException {
- //checkReadPermission(userId);
-
- return readFromDb(key, defaultValue, userId);
- }
-
- private String getLockPatternFilename(int userId) {
- String dataSystemDirectory =
- android.os.Environment.getDataDirectory().getAbsolutePath() +
- SYSTEM_DIRECTORY;
- if (userId == 0) {
- // Leave it in the same place for user 0
- return dataSystemDirectory + LOCK_PATTERN_FILE;
- } else {
- return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
- .getAbsolutePath();
- }
- }
-
- private String getLockPasswordFilename(int userId) {
- String dataSystemDirectory =
- android.os.Environment.getDataDirectory().getAbsolutePath() +
- SYSTEM_DIRECTORY;
- if (userId == 0) {
- // Leave it in the same place for user 0
- return dataSystemDirectory + LOCK_PASSWORD_FILE;
- } else {
- return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
- .getAbsolutePath();
- }
- }
-
- @Override
- public boolean havePassword(int userId) throws RemoteException {
- // Do we need a permissions check here?
-
- return new File(getLockPasswordFilename(userId)).length() > 0;
- }
-
- @Override
- public boolean havePattern(int userId) throws RemoteException {
- // Do we need a permissions check here?
-
- return new File(getLockPatternFilename(userId)).length() > 0;
- }
-
- @Override
- public void setLockPattern(byte[] hash, int userId) throws RemoteException {
- checkWritePermission(userId);
-
- writeFile(getLockPatternFilename(userId), hash);
- }
-
- @Override
- public boolean checkPattern(byte[] hash, int userId) throws RemoteException {
- checkPasswordReadPermission(userId);
- try {
- // Read all the bytes from the file
- RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r");
- final byte[] stored = new byte[(int) raf.length()];
- int got = raf.read(stored, 0, stored.length);
- raf.close();
- if (got <= 0) {
- return true;
- }
- // Compare the hash from the file with the entered pattern's hash
- return Arrays.equals(stored, hash);
- } catch (FileNotFoundException fnfe) {
- Slog.e(TAG, "Cannot read file " + fnfe);
- return true;
- } catch (IOException ioe) {
- Slog.e(TAG, "Cannot read file " + ioe);
- return true;
- }
- }
-
- @Override
- public void setLockPassword(byte[] hash, int userId) throws RemoteException {
- checkWritePermission(userId);
-
- writeFile(getLockPasswordFilename(userId), hash);
- }
-
- @Override
- public boolean checkPassword(byte[] hash, int userId) throws RemoteException {
- checkPasswordReadPermission(userId);
-
- try {
- // Read all the bytes from the file
- RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r");
- final byte[] stored = new byte[(int) raf.length()];
- int got = raf.read(stored, 0, stored.length);
- raf.close();
- if (got <= 0) {
- return true;
- }
- // Compare the hash from the file with the entered password's hash
- return Arrays.equals(stored, hash);
- } catch (FileNotFoundException fnfe) {
- Slog.e(TAG, "Cannot read file " + fnfe);
- return true;
- } catch (IOException ioe) {
- Slog.e(TAG, "Cannot read file " + ioe);
- return true;
- }
- }
-
- @Override
- public void removeUser(int userId) {
- checkWritePermission(userId);
-
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- try {
- File file = new File(getLockPasswordFilename(userId));
- if (file.exists()) {
- file.delete();
- }
- file = new File(getLockPatternFilename(userId));
- if (file.exists()) {
- file.delete();
- }
-
- db.beginTransaction();
- db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- private void writeFile(String name, byte[] hash) {
- try {
- // Write the hash to file
- RandomAccessFile raf = new RandomAccessFile(name, "rw");
- // Truncate the file if pattern is null, to clear the lock
- if (hash == null || hash.length == 0) {
- raf.setLength(0);
- } else {
- raf.write(hash, 0, hash.length);
- }
- raf.close();
- } catch (IOException ioe) {
- Slog.e(TAG, "Error writing to file " + ioe);
- }
- }
-
- 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);
-
- db.beginTransaction();
- try {
- db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
- new String[] {key, Integer.toString(userId)});
- db.insert(TABLE, null, cv);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- private String readFromDb(String key, String defaultValue, int userId) {
- Cursor cursor;
- String result = defaultValue;
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
- if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
- COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
- new String[] { Integer.toString(userId), key },
- null, null, null)) != null) {
- if (cursor.moveToFirst()) {
- result = cursor.getString(0);
- }
- cursor.close();
- }
- return result;
- }
-
- class DatabaseHelper extends SQLiteOpenHelper {
- private static final String TAG = "LockSettingsDB";
- private static final String DATABASE_NAME = "locksettings.db";
-
- private static final int DATABASE_VERSION = 1;
-
- public DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- setWriteAheadLoggingEnabled(true);
- }
-
- private void createTable(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE + " (" +
- "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
- COLUMN_KEY + " TEXT," +
- COLUMN_USERID + " INTEGER," +
- COLUMN_VALUE + " TEXT" +
- ");");
- }
-
- @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
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
- // Nothing yet
- }
- }
-
- private static final String[] VALID_SETTINGS = new String[] {
- LockPatternUtils.LOCKOUT_PERMANENT_KEY,
- LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
- LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
- LockPatternUtils.PASSWORD_TYPE_KEY,
- LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
- LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
- LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
- LockPatternUtils.LOCKSCREEN_OPTIONS,
- LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
- LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
- LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
- LockPatternUtils.PASSWORD_HISTORY_KEY,
- Secure.LOCK_PATTERN_ENABLED,
- Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
- Secure.LOCK_PATTERN_VISIBLE,
- Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
- };
-}
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 34cdd93..f10a2e8 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -61,6 +61,13 @@ public class PointerLocationView extends View implements InputDeviceListener {
private float mAltXVelocity;
private float mAltYVelocity;
+ // Current bounding box, if any
+ private boolean mHasBoundingBox;
+ private float mBoundingLeft;
+ private float mBoundingTop;
+ private float mBoundingRight;
+ private float mBoundingBottom;
+
// Position estimator.
private VelocityTracker.Estimator mEstimator = new VelocityTracker.Estimator();
private VelocityTracker.Estimator mAltEstimator = new VelocityTracker.Estimator();
@@ -388,6 +395,12 @@ public class PointerLocationView extends View implements InputDeviceListener {
ps.mCoords.x + orientationVectorX * tiltScale,
ps.mCoords.y + orientationVectorY * tiltScale,
3.0f, mPaint);
+
+ // Draw the current bounding box
+ if (ps.mHasBoundingBox) {
+ canvas.drawRect(ps.mBoundingLeft, ps.mBoundingTop,
+ ps.mBoundingRight, ps.mBoundingBottom, mPaint);
+ }
}
}
}
@@ -400,20 +413,20 @@ public class PointerLocationView extends View implements InputDeviceListener {
for (int i = 0; i < NI; i++) {
final int id = event.getPointerId(i);
event.getHistoricalPointerCoords(i, historyPos, mTempCoords);
- logCoords(type, action, i, mTempCoords, id,
- event.getToolType(i), event.getButtonState());
+ logCoords(type, action, i, mTempCoords, id, event);
}
}
for (int i = 0; i < NI; i++) {
final int id = event.getPointerId(i);
event.getPointerCoords(i, mTempCoords);
- logCoords(type, action, i, mTempCoords, id,
- event.getToolType(i), event.getButtonState());
+ logCoords(type, action, i, mTempCoords, id, event);
}
}
private void logCoords(String type, int action, int index,
- MotionEvent.PointerCoords coords, int id, int toolType, int buttonState) {
+ MotionEvent.PointerCoords coords, int id, MotionEvent event) {
+ final int toolType = event.getToolType(index);
+ final int buttonState = event.getButtonState();
final String prefix;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
@@ -483,6 +496,12 @@ public class PointerLocationView extends View implements InputDeviceListener {
.append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1)
.append(" VScroll=").append(coords.getAxisValue(MotionEvent.AXIS_VSCROLL), 1)
.append(" HScroll=").append(coords.getAxisValue(MotionEvent.AXIS_HSCROLL), 1)
+ .append(" BoundingBox=[(")
+ .append(event.getAxisValue(MotionEvent.AXIS_GENERIC_1), 3)
+ .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_2), 3).append(")")
+ .append(", (").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_3), 3)
+ .append(", ").append(event.getAxisValue(MotionEvent.AXIS_GENERIC_4), 3)
+ .append(")]")
.append(" ToolType=").append(MotionEvent.toolTypeToString(toolType))
.append(" ButtonState=").append(MotionEvent.buttonStateToString(buttonState))
.toString());
@@ -530,6 +549,8 @@ public class PointerLocationView extends View implements InputDeviceListener {
final PointerState ps = mPointers.get(id);
ps.mCurDown = true;
+ ps.mHasBoundingBox = (InputDevice.getDevice(event.getDeviceId()).
+ getMotionRange(MotionEvent.AXIS_GENERIC_1) != null);
}
final int NI = event.getPointerCount();
@@ -549,8 +570,7 @@ public class PointerLocationView extends View implements InputDeviceListener {
final PointerCoords coords = ps != null ? ps.mCoords : mTempCoords;
event.getHistoricalPointerCoords(i, historyPos, coords);
if (mPrintCoords) {
- logCoords("Pointer", action, i, coords, id,
- event.getToolType(i), event.getButtonState());
+ logCoords("Pointer", action, i, coords, id, event);
}
if (ps != null) {
ps.addTrace(coords.x, coords.y);
@@ -563,8 +583,7 @@ public class PointerLocationView extends View implements InputDeviceListener {
final PointerCoords coords = ps != null ? ps.mCoords : mTempCoords;
event.getPointerCoords(i, coords);
if (mPrintCoords) {
- logCoords("Pointer", action, i, coords, id,
- event.getToolType(i), event.getButtonState());
+ logCoords("Pointer", action, i, coords, id, event);
}
if (ps != null) {
ps.addTrace(coords.x, coords.y);
@@ -577,6 +596,13 @@ public class PointerLocationView extends View implements InputDeviceListener {
mAltVelocity.getEstimator(id, ps.mAltEstimator);
}
ps.mToolType = event.getToolType(i);
+
+ if (ps.mHasBoundingBox) {
+ ps.mBoundingLeft = event.getAxisValue(MotionEvent.AXIS_GENERIC_1, i);
+ ps.mBoundingTop = event.getAxisValue(MotionEvent.AXIS_GENERIC_2, i);
+ ps.mBoundingRight = event.getAxisValue(MotionEvent.AXIS_GENERIC_3, i);
+ ps.mBoundingBottom = event.getAxisValue(MotionEvent.AXIS_GENERIC_4, i);
+ }
}
}
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index b620568..fa29e6e 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -31,6 +31,8 @@ import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
@@ -179,6 +181,9 @@ public class ScrollingTabContainerView extends HorizontalScrollView
animateToTab(position);
}
}
+ if (mTabSpinner != null && position >= 0) {
+ mTabSpinner.setSelection(position);
+ }
}
public void setContentHeight(int contentHeight) {
@@ -378,6 +383,29 @@ public class ScrollingTabContainerView extends HorizontalScrollView
}
@Override
+ public void setSelected(boolean selected) {
+ final boolean changed = (isSelected() != selected);
+ super.setSelected(selected);
+ if (changed && selected) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ // This view masquerades as an action bar tab.
+ event.setClassName(ActionBar.Tab.class.getName());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ // This view masquerades as an action bar tab.
+ info.setClassName(ActionBar.Tab.class.getName());
+ }
+
+ @Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
index 273f6fe..ba113a3 100644
--- a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
+++ b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
@@ -225,13 +225,11 @@ public class SizeAdaptiveLayout extends ViewGroup {
if (unboundedView != null) {
tallestView = unboundedView;
}
- if (heightMode == MeasureSpec.UNSPECIFIED) {
- return tallestView;
- }
- if (heightSize > tallestViewSize) {
+ if (heightMode == MeasureSpec.UNSPECIFIED || heightSize > tallestViewSize) {
return tallestView;
+ } else {
+ return smallestView;
}
- return smallestView;
}
@Override
@@ -272,10 +270,10 @@ public class SizeAdaptiveLayout extends ViewGroup {
final int childWidth = mActiveChild.getMeasuredWidth();
final int childHeight = mActiveChild.getMeasuredHeight();
// TODO investigate setting LAYER_TYPE_HARDWARE on mLastActive
- mActiveChild.layout(0, 0, 0 + childWidth, 0 + childHeight);
+ mActiveChild.layout(0, 0, childWidth, childHeight);
if (DEBUG) Log.d(TAG, "got modesty offset of " + mModestyPanelTop);
- mModestyPanel.layout(0, mModestyPanelTop, 0 + childWidth, mModestyPanelTop + childHeight);
+ mModestyPanel.layout(0, mModestyPanelTop, childWidth, mModestyPanelTop + childHeight);
}
@Override
diff --git a/core/java/com/android/internal/widget/TransportControlView.java b/core/java/com/android/internal/widget/TransportControlView.java
index c33f038..ca797eb 100644
--- a/core/java/com/android/internal/widget/TransportControlView.java
+++ b/core/java/com/android/internal/widget/TransportControlView.java
@@ -143,7 +143,8 @@ public class TransportControlView extends FrameLayout implements OnClickListener
mLocalHandler = new WeakReference<Handler>(handler);
}
- public void setPlaybackState(int generationId, int state, long stateChangeTimeMs) {
+ public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
+ long currentPosMs, float speed) {
Handler handler = mLocalHandler.get();
if (handler != null) {
handler.obtainMessage(MSG_UPDATE_STATE, generationId, state).sendToTarget();
@@ -157,7 +158,7 @@ public class TransportControlView extends FrameLayout implements OnClickListener
}
}
- public void setTransportControlFlags(int generationId, int flags) {
+ public void setTransportControlInfo(int generationId, int flags, int posCapabilities) {
Handler handler = mLocalHandler.get();
if (handler != null) {
handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags)